Ticket #24425: 24425.draft.9.diff
File 24425.draft.9.diff, 54.1 KB (added by , 11 years ago) |
---|
-
wp-includes/revision.php
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 } -
wp-admin/admin-ajax.php
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( … … 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/includes/ajax-actions.php
2082 2082 wp_send_json($response); 2083 2083 } 2084 2084 2085 function wp_ajax_ revisions_data() {2086 check_ajax_referer( 'revisions-ajax-nonce', 'nonce' );2085 function wp_ajax_get_revision_diffs() { 2086 require ABSPATH . 'wp-admin/includes/revision.php'; 2087 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; 2088 // check_ajax_referer( 'revisions-ajax-nonce', 'nonce' ); 2096 2089 2097 $all_the_revisions = array(); 2098 if ( ! $post_id ) 2099 $post_id = $compare_to; 2090 if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) ) 2091 wp_send_json_error(); 2100 2092 2101 if ( ! current_user_can( 'read_post', $post _id) )2102 continue;2093 if ( ! current_user_can( 'read_post', $post->ID ) ) 2094 wp_send_json_error(); 2103 2095 2104 if ( ! $revisions = wp_get_post_revisions( $post_id ) ) 2105 return; 2096 // Really just pre-loading the cache here. 2097 if ( ! $revisions = wp_get_post_revisions( $post->ID ) ) 2098 wp_send_json_error(); 2106 2099 2107 $left_revision = get_post( $compare_to ); 2100 $return = array(); 2101 @set_time_limit( count( $_REQUEST['compare'] ) ); 2108 2102 2109 // single model fetch mode 2110 // return the diff of a single revision comparison 2111 if ( $single_revision_id ) { 2112 $right_revision = get_post( $single_revision_id ); 2103 foreach ( $_REQUEST['compare'] as $compare_key ) { 2104 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to 2113 2105 2114 if ( ! $compare_to ) 2115 $left_revision = get_post( $post_id ); 2116 2117 // make sure the right revision is the most recent, except on oldest revision 2118 if ( $compare_to && $right_revision->post_date < $left_revision->post_date ) { 2119 $temp = $left_revision; 2120 $left_revision = $right_revision; 2121 $right_revision = $temp; 2122 } 2123 2124 $lines_added = $lines_deleted = 0; 2125 $content = ''; 2126 // compare from left to right, passed from application 2127 foreach ( _wp_post_revision_fields() as $field => $field_value ) { 2128 $left_content = apply_filters( "_wp_post_revision_field_$field", $left_revision->$field, $field, $left_revision, 'left' ); 2129 $right_content = apply_filters( "_wp_post_revision_field_$field", $right_revision->$field, $field, $right_revision, 'right' ); 2130 2131 add_filter( "_wp_post_revision_field_$field", 'htmlspecialchars' ); 2132 2133 $args = array(); 2134 2135 if ( $show_split_view ) 2136 $args = array( 'show_split_view' => true ); 2137 2138 // compare_to == 0 means first revision, so compare to a blank field to show whats changed 2139 $diff = wp_text_diff_with_count( ( 0 == $compare_to ) ? '' : $left_content, $right_content, $args ); 2140 2141 if ( isset( $diff[ 'html' ] ) ) { 2142 $content .= sprintf( '<div class="diff-label">%s</div>', $field_value ); 2143 $content .= $diff[ 'html' ]; 2144 } 2145 2146 if ( isset( $diff[ 'lines_added' ] ) ) 2147 $lines_added = $lines_added + $diff[ 'lines_added' ]; 2148 2149 if ( isset( $diff[ 'lines_deleted' ] ) ) 2150 $lines_deleted = $lines_deleted + $diff[ 'lines_deleted' ]; 2151 } 2152 $content = '' == $content ? __( 'No difference' ) : $content; 2153 2154 $all_the_revisions = array ( 2155 'diff' => $content, 2156 'linesDeleted' => $lines_deleted, 2157 'linesAdded' => $lines_added 2106 $return[] = array( 2107 'id' => $compare_key, 2108 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ), 2158 2109 ); 2110 } 2159 2111 2160 echo json_encode( $all_the_revisions ); 2161 exit(); 2162 } // end single model fetch 2163 2164 $count = -1; 2165 2166 // reverse the list to start with oldest revision 2167 $revisions = array_reverse( $revisions ); 2168 2169 $previous_revision_id = 0; 2170 2171 /* translators: revision date format, see http://php.net/date */ 2172 $datef = _x( 'j F, Y @ G:i:s', 'revision date format'); 2173 2174 foreach ( $revisions as $revision ) : 2175 if ( ! $show_autosaves && wp_is_post_autosave( $revision ) ) 2176 continue; 2177 2178 $revision_from_date_author = ''; 2179 $is_current_revision = false; 2180 $count++; 2181 2182 /** 2183 * return blank data for diffs to the left of the left handle (for right handel model) 2184 * or to the right of the right handle (for left handel model) 2185 * and visa versa in RTL mode 2186 */ 2187 if( ! is_rtl() ) { 2188 if ( ( ( 0 != $left_handle_at && $count < $left_handle_at ) || 2189 ( 0 != $right_handle_at && $count > ( $right_handle_at - 2 ) ) ) ) { 2190 $all_the_revisions[] = array ( 2191 'ID' => $revision->ID, 2192 ); 2193 continue; 2194 } 2195 } else { // is_rtl 2196 if ( ( 0 != $left_handle_at && $count > ( $left_handle_at - 1 ) || 2197 ( 0 != $left_handle_at && $count < $right_handle_at ) ) ) { 2198 $all_the_revisions[] = array ( 2199 'ID' => $revision->ID, 2200 ); 2201 continue; 2202 } 2203 } 2204 2205 if ( $compare_two_mode ) { 2206 $compare_to_gravatar = get_avatar( $left_revision->post_author, 24 ); 2207 $compare_to_author = get_the_author_meta( 'display_name', $left_revision->post_author ); 2208 $compare_to_date = date_i18n( $datef, strtotime( $left_revision->post_modified ) ); 2209 2210 $revision_from_date_author = sprintf( 2211 /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */ 2212 _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ), 2213 $compare_to_gravatar, 2214 $compare_to_author, 2215 human_time_diff( strtotime( $left_revision->post_modified ), current_time( 'timestamp' ) ), 2216 $compare_to_date 2217 ); 2218 } 2219 2220 $gravatar = get_avatar( $revision->post_author, 24 ); 2221 $author = get_the_author_meta( 'display_name', $revision->post_author ); 2222 $date = date_i18n( $datef, strtotime( $revision->post_modified ) ); 2223 $revision_date_author = sprintf( 2224 /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */ 2225 _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ), 2226 $gravatar, 2227 $author, 2228 human_time_diff( strtotime( $revision->post_modified ), current_time( 'timestamp' ) ), 2229 $date 2230 ); 2231 2232 /* translators: 1: date */ 2233 $autosavef = _x( '%1$s [Autosave]', 'post revision title extra' ); 2234 /* translators: 1: date */ 2235 $currentf = _x( '%1$s [Current Revision]', 'post revision title extra' ); 2236 2237 if ( ! $post = get_post( $post_id ) ) 2238 continue; 2239 2240 if ( $left_revision->post_modified === $post->post_modified ) 2241 $revision_from_date_author = sprintf( $currentf, $revision_from_date_author ); 2242 elseif ( wp_is_post_autosave( $left_revision ) ) 2243 $revision_from_date_author = sprintf( $autosavef, $revision_from_date_author ); 2244 2245 if ( $revision->post_modified === $post->post_modified ) { 2246 $revision_date_author = sprintf( $currentf, $revision_date_author ); 2247 $is_current_revision = true; 2248 } elseif ( wp_is_post_autosave( $revision ) ) { 2249 $revision_date_author = sprintf( $autosavef, $revision_date_author ); 2250 } 2251 2252 /* translators: revision date short format, see http://php.net/date */ 2253 $date_short_format = _x( 'j M @ G:i', 'revision date short format'); 2254 $date_short = date_i18n( $date_short_format, strtotime( $revision->post_modified ) ); 2255 2256 $revision_date_author_short = sprintf( 2257 '%s <strong>%s</strong><br />%s', 2258 $gravatar, 2259 $author, 2260 $date_short 2261 ); 2262 2263 $restore_link = wp_nonce_url( 2264 add_query_arg( 2265 array( 'revision' => $revision->ID, 2266 'action' => 'restore' ), 2267 admin_url( 'revision.php' ) 2268 ), 2269 "restore-post_{$revision->ID}" 2270 ); 2271 2272 // if this is a left handled calculation swap data 2273 if ( 0 != $right_handle_at ) { 2274 $tmp = $revision_from_date_author; 2275 $revision_from_date_author = $revision_date_author; 2276 $revision_date_author = $tmp; 2277 } 2278 2279 if ( ( $compare_two_mode || -1 !== $previous_revision_id ) ) { 2280 $all_the_revisions[] = array ( 2281 'ID' => $revision->ID, 2282 'titleTo' => $revision_date_author, 2283 'titleFrom' => $revision_from_date_author, 2284 'titleTooltip' => $revision_date_author_short, 2285 'restoreLink' => urldecode( $restore_link ), 2286 'previousID' => $previous_revision_id, 2287 'isCurrent' => $is_current_revision, 2288 ); 2289 } 2290 $previous_revision_id = $revision->ID; 2291 2292 endforeach; 2293 2294 // in RTL + single handle mode, reverse the revision direction 2295 if ( is_rtl() && $compare_two_mode ) 2296 $all_the_revisions = array_reverse( $all_the_revisions ); 2297 2298 echo json_encode( $all_the_revisions ); 2299 exit(); 2112 wp_send_json_success( $return ); 2300 2113 } -
wp-admin/js/revisions.js
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 }; 6 revisions = wp.revisions = { model: {}, view: {}, controller: {} }; 9 7 10 _.extend( revisions, { model: {}, view: {}, controller: {} } );11 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 26 revisions.model.Revision = Backbone.Model.extend({}); 41 27 42 constructor: function() { 43 var self = this; 44 this.slider = new revisions.view.Slider(); 28 revisions.model.Revisions = Backbone.Collection.extend({ 29 model: revisions.model.Revision, 45 30 46 if ( null === this.revisions ) { 47 this.revisions = new Revisions(); // set up collection 48 this.startRightModelLoading(); 49 50 this.revisions.fetch({ // load revision data 51 success: function() { 52 self.stopRightModelLoading(); 53 self.completeApplicationSetup(); 54 } 55 }); 56 } 31 comparator: function( revision ) { 32 return revision.id; 57 33 }, 34 }); 58 35 59 loadDiffs: function( models ) { 60 var self = this, 61 revisionsToLoad = models.where( { completed: false } ), 62 delay = 0, 63 totalChanges; 36 revisions.model.Field = Backbone.Model.extend({}); 64 37 65 // match slider to passed revision_id 66 _.each( revisionsToLoad, function( revision ) { 67 if ( revision.get( 'ID' ) == revisions.model.settings.revision_id ) 68 self.rightDiff = self.revisions.indexOf( revision ) + 1; 69 }); 38 revisions.model.Fields = Backbone.Collection.extend({ 39 model: revisions.model.Field 40 }); 70 41 71 _.each( revisionsToLoad, function( revision ) { 72 _.delay( function() { 73 revision.fetch( { 74 update: true, 75 add: false, 76 remove: false, 77 success: function( model ) { 78 model.set( 'completed', true ); 42 revisions.model.Diff = Backbone.Model.extend({ 43 initialize: function(attributes, options) { 44 var fields = this.get('fields'); 45 this.unset('fields'); 79 46 80 // stop spinner when all models are loaded81 if ( 0 === models.where( { completed: false } ).length )82 self.stopModelLoadingSpinner();47 this.fields = new revisions.model.Fields( fields ); 48 } 49 }); 83 50 84 totalChanges = model.get( 'linesAdded' ) + model.get( 'linesDeleted' ), 85 scopeOfChanges = 'vsmall'; 51 revisions.model.Diffs = Backbone.Collection.extend({ 52 initialize: function(models, options) { 53 this.revisions = options.revisions; 54 this.requests = {}; 55 }, 86 56 87 // Note: hard coded scope of changes 88 // TODO change to dynamic based on range of values 89 if ( totalChanges > 1 && totalChanges <= 3 ) { 90 scopeOfChanges = 'small'; 91 } else if ( totalChanges > 3 && totalChanges <= 5 ) { 92 scopeOfChanges = 'med'; 93 } else if ( totalChanges > 5 && totalChanges <= 10 ) { 94 scopeOfChanges = 'large'; 95 } else if ( totalChanges > 10 ) { 96 scopeOfChanges = 'vlarge'; 97 } 98 model.set( 'scopeOfChanges', scopeOfChanges ); 99 if ( 0 !== self.rightDiff && 100 model.get( 'ID' ) === self.revisions.at( self.rightDiff - 1 ).get( 'ID' ) ) { 101 // reload if current model refreshed 102 self.revisionView.render(); 103 } 104 self.tickmarkView.render(); 105 } 106 } ); 107 }, delay ) ; 108 delay = delay + 150; // stagger model loads to avoid hammering server with requests 57 model: revisions.model.Diff, 58 59 ensure: function( id, context ) { 60 var diff = this.get( id ); 61 var request = this.requests[ id ]; 62 var deferred = $.Deferred(); 63 var ids = {}; 64 65 if ( diff ) { 66 deferred.resolveWith( context, [ diff ] ); 67 } else { 68 this.trigger( 'ensure:load', ids ); 69 _.each( ids, _.bind( function(id) { 70 // Remove anything that has an ongoing request 71 if ( this.requests[ id ] ) 72 delete ids[ id ]; 73 }, this ) ); 74 if ( ! request ) { 75 // Always include the ID that started this ensure 76 ids[ id ] = true; 77 request = this.load( _.keys( ids ) ); 109 78 } 110 );111 },112 79 113 startLeftModelLoading:function() {114 this.leftModelLoading = true;115 $('#revision-diff-container').addClass('left-model-loading');116 },80 request.done( _.bind( function() { 81 deferred.resolveWith( context, [ this.get( id ) ] ); 82 }, this ) ); 83 } 117 84 118 stopLeftModelLoading: function() { 119 this.leftModelLoading = false; 85 return deferred.promise(); 120 86 }, 121 87 122 startRightModelLoading: function() { 123 this.rightModelLoading = true; 124 $('#revision-diff-container').addClass('right-model-loading'); 88 loadNew: function( comparisons ) { 89 comparisons = _.object( comparisons, comparisons ); 90 _.each( comparisons, _.bind( function( id ) { 91 // Exists 92 if ( this.get( id ) ) 93 delete comparisons[ id ]; 94 }, this ) ); 95 comparisons = _.toArray( comparisons ); 96 console.log( 'Loading', comparisons ); 97 return this.load( comparisons ); 125 98 }, 126 99 127 stopRightModelLoading: function() { 128 this.rightModelLoading = false; 100 load: function( comparisons ) { 101 // Our collection should only ever grow, never shrink, so remove: false 102 return this.fetch({ data: { compare: comparisons }, remove: false }); 129 103 }, 130 104 131 stopModelLoadingSpinner: function() { 132 $('#revision-diff-container').removeClass('right-model-loading'); 133 $('#revision-diff-container').removeClass('left-model-loading'); 105 /**/ 106 loadLast: function( num ) { 107 num = num || 1; 108 var ids = this.getProximalDiffIds(); 109 ids = _.last( ids, num ); 110 if ( ids.length ) { 111 console.log( 'Last ' + num, ids ); 112 return this.loadNew( ids ); 113 } 134 114 }, 135 115 136 reloadModel: function() { 137 if ( this.singleRevision ) { 138 this.reloadModelSingle(); 139 } else { 140 this.reloadLeftRight(); 116 loadLastUnloaded: function( num ) { 117 num = num || 1; 118 var ids = this.getUnloadedProximalDiffIds(); 119 ids = _.last( ids, num ); 120 if ( ids.length ) { 121 console.log( 'Loading last ' + num ); 122 return this.loadNew( ids ); 141 123 } 142 124 }, 143 125 144 // load the models for the single handle mode 145 reloadModelSingle: function() { 146 var self = this; 126 getProximalDiffIds: function() { 127 var previous = 0, ids = []; 128 this.revisions.each( _.bind( function(revision) { 129 ids.push( previous + ':' + revision.id ); 130 previous = revision.id; 131 }, this ) ); 132 return ids; 133 }, 147 134 148 self.startRightModelLoading(); 135 getUnloadedProximalDiffIds: function() { 136 var comparisons = this.getProximalDiffIds(); 137 comparisons = _.object( comparisons, comparisons ); 138 _.each( comparisons, _.bind( function( id ) { 139 // Exists 140 if ( this.get( id ) ) 141 delete comparisons[ id ]; 142 }, this ) ); 143 return _.toArray( comparisons ); 144 }, 149 145 150 self.revisions.reload({ 151 options: { 152 'showAutosaves': self.autosaves, 153 'showSplitView': self.showSplitView 154 }, 146 loadAllBy: function( chunkSize ) { 147 chunkSize = chunkSize || 20; 148 var unloaded = this.getUnloadedProximalDiffIds(); 149 if ( unloaded.length ) { 150 return this.loadLastUnloaded( chunkSize ).always( _.bind( function() { 151 this.loadAllBy( chunkSize ); 152 }, this ) ); 153 } 154 }, 155 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 }, 156 /**/ 168 157 169 error: function() { 170 self.stopRightModelLoading(); 171 } 172 }); 173 }, 158 sync: function( method, model, options ) { 159 if ( 'read' === method ) { 160 options = options || {}; 161 options.context = this; 162 options.data = _.extend( options.data || {}, { 163 action: 'get-revision-diffs', 164 post_id: revisions.settings.postId 165 }); 174 166 175 // load the models for the left handle (the right handler has moved) 176 reloadLeft: function() { 177 var self = this; 178 self.startLeftModelLoading(); 179 self.leftHandleRevisions = new Revisions( {}, { 180 'compareTo': self.revisions.at( self.rightDiff - 1 ).get( 'ID' ), // diff and model count off by 1 181 'showAutosaves': self.autosaves, 182 'showSplitView': self.showSplitView, 183 'rightHandleAt': self.rightDiff 184 }); 167 var deferred = wp.xhr.send( options ); 168 var requests = this.requests; 185 169 186 self.leftHandleRevisions.fetch({ 187 success: function(){ 188 self.stopLeftModelLoading(); 189 self.loadDiffs( self.leftHandleRevisions ); 190 self.tickmarkView.model = self.leftHandleRevisions; 191 self.slider.refresh({ 192 'max': self.revisions.length 170 // Record that we're requesting each diff. 171 if ( options.data.compare ) { 172 _.each( options.data.compare, function( id ) { 173 requests[ id ] = deferred; 193 174 }); 194 // ensure right handle not beyond length195 if ( self.rightDiff > self.revisions.length )196 self.rightDiff = self.revisions.length;197 },198 199 error: function() {200 self.stopLeftModelLoading();201 175 } 202 });203 },204 176 205 // load the models for the right handle (the left handle has moved) 206 reloadRight: function() { 207 var self = this; 208 self.startRightModelLoading(); 209 self.rightHandleRevisions = new Revisions( {}, { 210 'compareTo': self.revisions.at( self.leftDiff - 1 ).get( 'ID' ), // diff and model count off by 1 211 'showAutosaves': self.autosaves, 212 'showSplitView': self.showSplitView, 213 'leftHandleAt': self.leftDiff 214 }); 177 // When the request completes, clear the stored request. 178 deferred.always( function() { 179 if ( options.data.compare ) { 180 _.each( options.data.compare, function( id ) { 181 delete requests[ id ]; 182 }); 183 } 184 }); 215 185 216 self.rightHandleRevisions.fetch({ 217 success: function(){ 218 self.stopRightModelLoading(); 219 self.loadDiffs( self.rightHandleRevisions ); 220 self.tickmarkView.model = self.rightHandleRevisions; 221 self.slider.refresh({ 222 'max': self.revisions.length 223 }, true); 224 }, 186 return deferred; 225 187 226 error: function( response ) { 227 self.stopRightModelLoading(); 228 } 229 }); 188 // Otherwise, fall back to `Backbone.sync()`. 189 } else { 190 return Backbone.Model.prototype.sync.apply( this, arguments ); 191 } 192 } 193 }); 230 194 231 },232 195 233 /** 234 * reloadLeftRight reload models for both the left and right handles 235 */ 236 reloadLeftRight: function() { 237 this.startRightModelLoading(); 238 this.startLeftModelLoading(); 239 this.reloadLeft(); 240 this.reloadRight(); 241 }, 196 revisions.model.FrameState = Backbone.Model.extend({ 197 initialize: function( attributes, options ) { 198 this.revisions = options.revisions; 199 this.diffs = new revisions.model.Diffs( [], {revisions: this.revisions} ); 242 200 243 disabledButtonCheck: function( val ) { 244 var maxVal = this.revisions.length - 1, 245 next = ! isRtl ? $( '#next' ) : $( '#previous' ), 246 prev = ! isRtl ? $( '#previous' ) : $( '#next' ); 247 248 // Disable "Next" button if you're on the last node 249 if ( maxVal === val ) 250 next.prop( 'disabled', true ); 251 else 252 next.prop( 'disabled', false ); 253 254 // Disable "Previous" button if you're on the 0 node 255 if ( 0 === val ) 256 prev.prop( 'disabled', true ); 257 else 258 prev.prop( 'disabled', false ); 201 this.listenTo( this, 'change:from change:to', this.updateDiffId ); 259 202 }, 260 203 261 /** 262 * completeApplicationSetup finishes loading all views once the initial model load is complete 263 */ 264 completeApplicationSetup: function() { 265 this.revisionView = new revisions.view.Diff({ 266 model: this.revisions 267 }); 268 this.revisionView.render(); // render the revision view 269 270 this.loadDiffs( this.revisions ); // get the actual revisions data 271 272 this.revisionsInteractions = new revisions.view.Interact({ 273 model: this.revisions 274 }); 275 this.revisionsInteractions.render(); // render the interaction view 276 277 this.tickmarkView = new revisions.view.Tickmarks({ 278 model: this.revisions 279 }); 280 this.tickmarkView.render(); // render the tickmark view 204 updateDiffId: function() { 205 var from = this.get('from'); 206 var to = this.get('to'); 207 this.set( 'diffId', (from ? from.id : '0') + ':' + to.id ); 281 208 } 282 209 }); 283 210 … … 288 215 * ======================================================================== 289 216 */ 290 217 291 /** 292 * wp.revisions.view.Slider 293 * 294 * The slider 295 */ 296 revisions.view.Slider = Backbone.View.extend({ 297 el: $( '#diff-slider' ), 298 singleRevision: true, 218 // The frame view. This contains the entire page. 219 revisions.view.Frame = wp.Backbone.View.extend({ 220 tagName: 'div', 221 className: 'revisions', 222 template: wp.template('revisions-frame'), 299 223 300 initialize: function( options ) { 301 this.options = _.defaults( options || {}, { 302 value: 0, 303 min: 0, 304 max: 1, 305 step: 1 224 initialize: function() { 225 this.model = new revisions.model.FrameState({}, { 226 revisions: this.collection 306 227 }); 307 },308 228 309 /** 310 * respond to slider slide events 311 * Note: in one handle mode, jQuery UI reports leftmost position as 0 312 * in two handle mode, jQuery UI Slider reports leftmost position as 1 313 */ 314 slide: function( event, ui ) { 315 if ( this.singleRevision ) { 316 Diff.rightDiff = ( ui.value + 1 ); 317 Diff.revisionView.render(); 318 Diff.disabledButtonCheck( ui.value ); 319 } else { 320 if ( ui.values[0] === ui.values[1] ) // prevent compare to self 321 return false; 229 this.listenTo( this.model, 'change:diffId', this.updateDiff ); 322 230 323 if ( $( ui.handle ).hasClass( 'left-handle' ) ) { 324 // Left handler 325 if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle 326 return false; 231 this.views.set( '.revisions-control-frame', new revisions.view.Controls({ 232 model: this.model 233 }) ); 327 234 328 Diff.leftDiff = isRtl ? ui.values[1] : ui.values[0]; // handles are reversed in RTL mode 329 } else { 330 // Right handler 331 if ( Diff.rightModelLoading ) // right model still loading, prevent sliding right handle 332 return false; 235 if ( this.model.revisions.length ) { 236 var last = this.model.revisions.last(2); 237 var attributes = { to: last.pop() }; 333 238 334 Diff.rightDiff = isRtl ? ui.values[0] : ui.values[1]; // handles are reversed in RTL mode335 }239 if ( last.length ) 240 attributes.from = last.pop(); 336 241 337 Diff.revisionView.render(); 338 } 339 }, 242 this.model.set( attributes ); 340 243 341 /** 342 * responds to slider start sliding events 343 * in two handle mode stores start position, so if unchanged at stop event no need to reload diffs 344 * also swaps in the appropriate models - left handled or right handled 345 */ 346 start: function( event, ui ) { 347 // Not needed in one mode 348 if ( this.singleRevision ) 349 return; 350 351 if ( $( ui.handle ).hasClass( 'left-handle' ) ) { 352 // Left handler 353 if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle 354 return false; 355 356 Diff.revisionView.draggingLeft = true; 357 358 if ( Diff.revisionView.model !== Diff.leftHandleRevisions && 359 null !== Diff.leftHandleRevisions ) { 360 Diff.revisionView.model = Diff.leftHandleRevisions; // use the left handle models 361 Diff.tickmarkView.model = Diff.leftHandleRevisions; 362 Diff.tickmarkView.render(); 363 } 364 365 Diff.leftDiffStart = isRtl ? ui.values[1] : ui.values[0]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first 366 367 } else { 368 // Right handler 369 if ( Diff.rightModelLoading || 0 === Diff.rightHandleRevisions.length) // right model still loading, prevent sliding right handle 370 return false; 371 372 if ( Diff.revisionView.model !== Diff.rightHandleRevisions && 373 null !== Diff.rightHandleRevisions ) { 374 Diff.revisionView.model = Diff.rightHandleRevisions; // use the right handle models 375 Diff.tickmarkView.model = Diff.rightHandleRevisions; 376 Diff.tickmarkView.render(); 377 } 378 379 Diff.revisionView.draggingLeft = false; 380 Diff.rightDiffStart = isRtl ? ui.values[0] : ui.values[1]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first 244 // Load the rest: first 10, then the rest by 50 245 this.model.diffs.loadLastUnloaded( 10 ).always( _.bind( function() { 246 console.log( 'Loading all by 50' ); 247 this.model.diffs.loadAllBy( 50 ); 248 }, this ) ); 381 249 } 382 250 }, 383 251 384 /** 385 * responds to slider stop events 386 * in two handled mode, if the handle that stopped has moved, reload the diffs for the other handle 387 * the other handle compares to this handle's position, so if it changes they need to be recalculated 388 */ 389 stop: function( event, ui ) { 390 // Not needed in one mode 391 if ( this.singleRevision ) 392 return; 252 render: function() { 253 wp.Backbone.View.prototype.render.apply( this, arguments ); 393 254 394 // calculate and generate a diff for comparing to the left handle 395 // and the right handle, swap out when dragging 396 if ( $( ui.handle ).hasClass( 'left-handle' ) ) { 397 // Left handler 398 if ( Diff.leftDiffStart !== isRtl ? ui.values[1] : ui.values[0] ) // in RTL mode the 'left handle' is the second in the slider, 'right' is first 399 Diff.reloadRight(); 400 } else { 401 // Right handler 402 if ( Diff.rightDiffStart !== isRtl ? ui.values[0] : ui.values[1] ) // in RTL mode the 'left handle' is the second in the slider, 'right' is first 403 Diff.reloadLeft(); 404 } 405 }, 255 $('#wpbody-content .wrap').append( this.el ); 256 this.views.ready(); 406 257 407 addTooltip: function( handle, message ) { 408 handle.find( '.ui-slider-tooltip' ).html( message ); 258 return this; 409 259 }, 410 260 411 width: function() { 412 return $( '#diff-slider' ).width(); 413 }, 261 updateDiff: function() { 262 this.model.diffs.ensure( this.model.get('diffId'), this ).done( function( diff ) { 263 if ( this.model.get('diffId') !== diff.id ) 264 return; 265 this.views.set( '.revisions-diff-frame', new revisions.view.Diff({ 266 model: diff 267 })); 268 }); 269 } 270 }); 414 271 415 setWidth: function( width ) { 416 $( '#diff-slider' ).width( width ); 417 }, 272 // The control view. 273 // This contains the revision slider, previous/next buttons, and the compare checkbox. 274 revisions.view.Controls = wp.Backbone.View.extend({ 275 tagName: 'div', 276 className: 'revisions-controls', 418 277 419 refresh: function( options, slide ) { 420 $( '#diff-slider' ).slider( 'option', options ); 421 422 // Triggers the slide event 423 if ( slide ) 424 $( '#diff-slider' ).trigger( 'slide' ); 425 426 Diff.disabledButtonCheck( options.value ); 427 }, 428 429 option: function( key ) { 430 return $( '#diff-slider' ).slider( 'option', key ); 431 }, 432 433 render: function() { 434 var self = this; 435 // this.$el doesn't work, why? 436 $( '#diff-slider' ).slider( { 437 slide: $.proxy( self.slide, self ), 438 start: $.proxy( self.start, self ), 439 stop: $.proxy( self.stop, self ) 440 } ); 441 442 // Set options 443 this.refresh( this.options ); 278 initialize: function() { 279 //this.$el.html( this.template ); 280 this.views.add( new revisions.view.Buttons({ 281 model: this.model 282 })); 283 this.views.add( new revisions.view.Slider({ 284 model: this.model 285 }) ); 444 286 } 445 287 }); 446 288 447 /** 448 * wp.revisions.view.Tickmarks 449 * 450 * The slider tickmarks. 451 */ 452 revisions.view.Tickmarks = Backbone.View.extend({ 453 el: $('#diff-slider-ticks'), 454 template: wp.template('revision-ticks'), 455 model: Revision, 289 // The buttons view. 290 // Encapsulates all of the configuration for the previous/next buttons, and the compare checkbox. 291 revisions.view.Buttons = wp.Backbone.View.extend({ 292 tagName: 'div', 293 className: 'revisions-buttons', 294 template: wp.template('revisions-controls'), 456 295 457 resetTicks: function() { 458 var sliderMax, sliderWidth, adjustMax, tickWidth, tickCount = 0, aTickWidth, tickMargin, self = this, firstTick, lastTick; 459 sliderMax = Diff.slider.option( 'max' ); 460 sliderWidth = Diff.slider.width(); 461 adjustMax = Diff.singleRevision ? 0 : 1; 462 tickWidth = Math.floor( sliderWidth / ( sliderMax - adjustMax ) ); 463 tickWidth = ( tickWidth > 50 ) ? 50 : tickWidth; // set minimum and maximum widths for tick marks 464 tickWidth = ( tickWidth < 6 ) ? 6 : tickWidth; 465 sliderWidth = tickWidth * ( sliderMax - adjustMax ); // calculate the slider width 466 aTickWidth = $( '.revision-tick' ).width(); 467 468 if ( tickWidth !== aTickWidth ) { // is the width already set correctly? 469 $( '.revision-tick' ).each( function() { 470 tickMargin = Math.floor( ( tickWidth - $( this ).width() ) / 2 ) + 1; 471 $( this ).css( 'border-left', tickMargin + 'px solid #f7f7f7'); // space the ticks out using margins 472 $( this ).css( 'border-right', ( tickWidth - tickMargin - $( this ).width() ) + 'px solid #f7f7f7'); // space the ticks out using margins 473 }); 474 firstTick = $( '.revision-tick' ).first(); //cache selectors for optimization 475 lastTick = $( '.revision-tick' ).last(); 476 477 sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( lastTick.outerWidth() - lastTick.innerWidth() ) ) / 2 ); // room for the last tick 478 sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( firstTick.outerWidth() - firstTick.innerWidth() ) ) / 2 ); // room for the first tick 479 firstTick.css( 'border-left', 'none' ); // first tick gets no left border 480 lastTick.css( 'border-right', 'none' ); // last tick gets no right border 481 } 482 483 /** 484 * reset the slider width 485 */ 486 Diff.slider.setWidth( sliderWidth ); 487 $( '.diff-slider-ticks-wrapper' ).width( sliderWidth ); 488 $( '#diff-slider-ticks' ).width( sliderWidth ); 489 490 /** 491 * go through all ticks, add hover and click interactions 492 */ 493 $( '.revision-tick' ).each( function() { 494 Diff.slider.addTooltip ( $( this ), Diff.revisions.at( tickCount++ ).get( 'titleTooltip' ) ); 495 $( this ).hover( 496 function() { 497 $( this ).find( '.ui-slider-tooltip' ).show().append('<div class="arrow"></div>'); 498 }, 499 function() { 500 $( this ).find( '.ui-slider-tooltip' ).hide().find( '.arrow' ).remove(); 501 } 502 ); 503 504 /** 505 * move the slider handle when the tick marks are clicked 506 */ 507 $( this ).on( 'click', 508 { tickCount: tickCount }, // pass the tick through so we know where to move the handle 509 function( event ) { 510 if ( Diff.slider.singleRevision ) { // single handle mode 511 Diff.rightDiff = event.data.tickCount; // reposition the right handle 512 Diff.slider.refresh({ 513 value: Diff.rightDiff - 1 514 } ); 515 } else { //compare two mode 516 if ( isRtl ) { 517 if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side 518 Diff.rightDiff = event.data.tickCount; // set the 'right' handle location 519 Diff.reloadLeft(); // reload the left handle comparison models 520 } else { // middle or 'right' clicks 521 Diff.leftDiff = event.data.tickCount; // set the 'left' handle location 522 Diff.reloadRight(); // reload right handle models 523 } 524 } else { 525 if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side 526 Diff.leftDiff = event.data.tickCount; // set the left handle location 527 Diff.reloadRight(); // reload the right handle comparison models 528 } else { // middle or 'right' clicks 529 Diff.rightDiff = event.data.tickCount; // set the right handle location 530 Diff.reloadLeft(); // reload left handle models 531 } 532 } 533 Diff.slider.refresh( { // set the slider handle positions 534 values: [ isRtl ? Diff.rightDiff : Diff.leftDiff, isRtl ? Diff.leftDiff : Diff.rightDiff ] 535 } ); 536 } 537 Diff.revisionView.render(); // render the main view 538 } ); 539 } ); 296 initialize: function() { 297 this.$el.html( this.template ) 540 298 }, 541 299 542 // render the tick mark view543 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.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 300 events: { 570 'click #next': ! isRtl ? 'nextRevision' : 'previousRevision',571 'click #previous': ! isRtl ? 'previousRevision' : 'nextRevision'301 'click #next': 'nextRevision', 302 'click #previous': 'previousRevision' 572 303 }, 304 305 nextRevision: function() { 306 var toIndex = this.model.revisions.indexOf( this.model.get( 'to' ) ); 307 toIndex = isRtl ? toIndex - 1 : toIndex + 1; 308 this.model.set( { 'to': this.model.revisions.at( toIndex ) } ); 309 }, 310 311 previousRevision: function() { 312 var toIndex = this.model.revisions.indexOf( this.model.get('to') ); 313 toIndex = isRtl ? toIndex + 1 : toIndex - 1; 314 this.model.set( { 'to': this.model.revisions.at( toIndex ) } ); 315 }, 573 316 574 render: function() { 575 var modelcount; 576 this.$el.html( this.template ); 577 578 modelcount = Diff.revisions.length; 579 580 Diff.slider.singleRevision = Diff.singleRevision; 581 Diff.slider.render(); 582 583 if ( Diff.singleRevision ) { 584 Diff.slider.refresh({ 585 value: Diff.rightDiff - 1, // rightDiff value is off model index by 1 586 min: 0, 587 max: modelcount - 1 588 }); 589 590 $( '#revision-diff-container' ).removeClass( 'comparing-two-revisions' ); 591 592 } else { 593 Diff.slider.refresh({ 594 // in RTL mode the 'left handle' is the second in the slider, 'right' is first 595 values: [ isRtl ? Diff.rightDiff : Diff.leftDiff, isRtl ? Diff.leftDiff : Diff.rightDiff ], 596 min: 1, 597 max: modelcount + 1, 598 range: true 599 }); 600 601 $( '#revision-diff-container' ).addClass( 'comparing-two-revisions' ); 602 // in RTL mode the 'left handle' is the second in the slider, 'right' is first 603 $( '#diff-slider a.ui-slider-handle' ).first().addClass( isRtl ? 'right-handle' : 'left-handle' ); 604 $( '#diff-slider a.ui-slider-handle' ).last().addClass( isRtl ? 'left-handle' : 'right-handle' ); 605 606 } 607 608 return this; 317 ready: function() { 318 this.listenTo( this.model, 'change:diffId', this.disabledButtonCheck ); 609 319 }, 610 320 611 // go to the next revision 612 nextRevision: function() { 613 if ( Diff.rightDiff < this.model.length ) // unless at right boundry 614 Diff.rightDiff = Diff.rightDiff + 1 ; 321 // Check to see if the Previous or Next buttons need to be disabled or enabled 322 disabledButtonCheck: function() { 323 var maxVal = isRtl ? 0 : this.model.revisions.length - 1, 324 minVal = isRtl ? this.model.revisions.length - 1 : 0, 325 next = $( '.revisions-next .button' ), 326 previous = $( '.revisions-previous .button' ), 327 val = this.model.revisions.indexOf( this.model.get( 'to' ) ); 615 328 616 Diff.revisionView.render(); 329 // Disable "Next" button if you're on the last node 330 if ( maxVal === val ) 331 next.prop( 'disabled', true ); 332 else 333 next.prop( 'disabled', false ); 617 334 618 Diff.slider.refresh({ 619 value: Diff.rightDiff - 1 620 }, true ); 335 // Disable "Previous" button if you're on the first node 336 if ( minVal === val ) 337 previous.prop( 'disabled', true ); 338 else 339 previous.prop( 'disabled', false ); 621 340 }, 622 341 623 // go to the previous revision624 previousRevision: function() {625 if ( Diff.rightDiff > 1 ) // unless at left boundry626 Diff.rightDiff = Diff.rightDiff - 1 ;627 342 628 Diff.revisionView.render();629 630 Diff.slider.refresh({631 value: Diff.rightDiff - 1632 }, true );633 }634 343 }); 635 344 636 /** 637 * wp.revisions.view.Diff 638 * 639 * Diff, compare two checkbox and restore button 640 */ 641 revisions.view.Diff = Backbone.View.extend({ 642 el: $( '#revisions-diff' ), 643 template: wp.template( 'revisions-diff' ), 644 draggingLeft: false, 345 // The slider view. 346 // Encapsulates all of the configuration for the jQuery UI slider into a view. 347 revisions.view.Slider = wp.Backbone.View.extend({ 348 tagName: 'div', 349 className: 'wp-slider', 645 350 646 // the compare two button is in this view, add the interaction here 647 events: { 648 'click #compare-two-revisions': 'compareTwo', 649 'click #restore-revision': 'restore' 650 }, 351 initialize: function() { 352 _.bindAll( this, 'start', 'slide', 'stop' ); 651 353 652 // render the revisions653 render: function() {654 var addHtml = '', thediff;354 // Create the slider model from the provided collection data. 355 // TODO: This should actually pull from the model's `to` key. 356 var latestRevisionIndex = this.model.revisions.length - 1; 655 357 656 // compare two revisions mode? 657 if ( ! Diff.singleRevision ) { 658 if ( this.draggingLeft ) { 659 thediff = Diff.leftDiff - 1; //leftDiff value is off model index by 1 660 if ( this.model.at( thediff ) ) { 661 addHtml = this.template( this.model.at( thediff ).toJSON() ); 662 } 663 } else { // dragging right handle 664 thediff = Diff.rightDiff - 1; // rightDiff value is off model index by 1 665 if ( this.model.at( thediff ) ) { 666 addHtml = this.template( this.model.at( thediff ).toJSON() ); 667 } 668 } 669 } else { // end compare two revisions mode, eg only one slider handle 670 if ( this.model.at( Diff.rightDiff - 1 ) ) { // rightDiff value is off model index by 1 671 addHtml = this.template( this.model.at( Diff.rightDiff - 1 ).toJSON() ); 672 } 673 } 674 this.$el.html( addHtml ); 358 // Find the initially selected revision 359 var initiallySelectedRevisionIndex = 360 this.model.revisions.indexOf( this.model.revisions.findWhere( { id: Number( revisions.settings.selectedRevision ) } ) ); 675 361 676 if ( this.model.length < 2 ) { 677 $( '#diff-slider' ).hide(); // don't allow compare two if fewer than three revisions 678 $( '.diff-slider-ticks-wrapper' ).hide(); 679 } 362 this.settings = new revisions.model.Slider({ 363 max: latestRevisionIndex, 364 value: initiallySelectedRevisionIndex, 365 start: this.start, 366 slide: this.slide, 367 stop: this.stop 368 }); 369 }, 680 370 681 this.toggleCompareTwoCheckbox(); 371 ready: function() { 372 this.$el.slider( this.settings.toJSON() ); 373 this.settings.on( 'change', function( model, options ) { 374 // Apply changes to slider settings here. 375 this.$el.slider( { value: this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); // Set handle to current to model 376 }, this ); 377 // Reset to the initially selected revision 378 this.slide( '', this.settings.attributes ); 682 379 683 // hide the restore button when on the last sport/current post data684 $( '#restore-revision' ).toggle( ! Diff.revisions.at( Diff.rightDiff - 1 ).get( 'isCurrent' ));380 // Listen for changes in the diffId 381 this.listenTo( this.model, 'change:diffId', this.diffIdChanged ); 685 382 686 return this;687 383 }, 688 384 689 toggleCompareTwoCheckbox: function() { 690 // don't allow compare two if fewer than three revisions 691 if ( this.model.length < 3 ) 692 $( '#toggle-revision-compare-mode' ).hide(); 693 694 $( '#compare-two-revisions' ).prop( 'checked', ! Diff.singleRevision ); 385 diffIdChanged: function() { 386 // Reset the view settings when diffId is changed 387 this.settings.set( { 'value': this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); 695 388 }, 696 389 697 // turn on/off the compare two mode 698 compareTwo: function() { 699 if ( $( '#compare-two-revisions' ).is( ':checked' ) ) { // compare 2 mode 700 Diff.singleRevision = false ; 390 start: function() { 701 391 702 // in RTL mode handles are swapped, so boundary checks are different; 703 if ( isRtl ){ 704 Diff.leftDiff = Diff.revisions.length; // put the left handle at the rightmost position, representing current revision 392 }, 705 393 706 if ( Diff.revisions.length === Diff.rightDiff ) // make sure 'left' handle not in rightmost slot 707 Diff.rightDiff = Diff.rightDiff - 1; 708 } else { 709 if ( 1 === Diff.rightDiff ) // make sure right handle not in leftmost slot 710 Diff.rightDiff = 2; 711 } 394 slide: function( event, ui ) { 395 var attributes = { 396 to: this.model.revisions.at( ui.value ) 397 }; 712 398 713 Diff.revisionView.draggingLeft = false; 399 // If we're at the first revision, unset 'from'. 400 if ( ui.value ) 401 attributes.from = this.model.revisions.at( ui.value - 1 ); 402 else 403 this.model.unset('from', { silent: true }); 714 404 715 revisions.model.settings.revision_id = ''; // reset passed revision id so switching back to one handle mode doesn't re-select revision 716 Diff.reloadLeftRight(); // load diffs for left and right handles 717 Diff.revisionView.model = Diff.rightHandleRevisions; 718 719 } else { // compare one mode 720 Diff.singleRevision = true; 721 Diff.revisionView.draggingLeft = false; 722 Diff.reloadModelSingle(); 723 } 724 Diff.revisionsInteractions.render(); 725 Diff.tickmarkView.render(); 405 this.model.set( attributes ); 726 406 }, 727 407 728 restore: function() { 729 document.location = $( '#restore-revision' ).data( 'restoreLink' ); 408 stop: function() { 730 409 } 731 410 }); 732 411 412 // The diff view. 413 // This is the view for the current active diff. 414 revisions.view.Diff = wp.Backbone.View.extend({ 415 tagName: 'div', 416 className: 'revisions-diff', 417 template: wp.template('revisions-diff'), 733 418 734 /** 735 * ======================================================================== 736 * MODELS 737 * ======================================================================== 738 */ 739 740 /** 741 * wp.revisions.Revision 742 */ 743 Revision = revisions.model.Revision = Backbone.Model.extend({ 744 idAttribute: 'ID', 745 746 defaults: { 747 ID: 0, 748 titleTo: '', 749 titleTooltip: '', 750 titleFrom: '', 751 diff: '<div class="diff-loading"><div class="spinner"></div></div>', 752 restoreLink: '', 753 completed: false, 754 linesAdded: 0, 755 linesDeleted: 0, 756 scopeOfChanges: 'none', 757 previousID: 0, 758 isCurrent: false 759 }, 760 761 url: function() { 762 if ( Diff.singleRevision ) { 763 return ajaxurl + 764 '?action=revisions-data' + 765 '&show_autosaves=true' + 766 '&show_split_view=true' + 767 '&nonce=' + revisions.model.settings.nonce + 768 '&single_revision_id=' + this.id + 769 '&compare_to=' + this.get( 'previousID' ) + 770 '&post_id=' + revisions.model.settings.post_id; 771 } else { 772 return this.collection.url() + '&single_revision_id=' + this.id; 773 } 774 419 // Generate the options to be passed to the template. 420 prepare: function() { 421 return _.extend({ fields: this.model.fields.toJSON() }, this.options ); 775 422 } 776 423 }); 777 424 778 /** 779 * wp.revisions.Revisions 780 */ 781 Revisions = revisions.Revisions = Backbone.Collection.extend({ 782 model: Revision, 425 // Initialize the revisions UI. 426 revisions.init = function() { 427 revisions.view.frame = new revisions.view.Frame({ 428 collection: new revisions.model.Revisions( revisions.settings.revisionData ) 429 }).render(); 430 }; 783 431 784 initialize: function( models, options ) { 785 this.options = _.defaults( options || {}, { 786 'compareTo': revisions.model.settings.post_id, 787 'post_id': revisions.model.settings.post_id, 788 'showAutosaves': true, 789 'showSplitView': true, 790 'rightHandleAt': 0, 791 'leftHandleAt': 0, 792 'nonce': revisions.model.settings.nonce 793 }); 794 }, 795 796 url: function() { 797 return ajaxurl + 798 '?action=revisions-data' + 799 '&compare_to=' + this.options.compareTo + // revision are we comparing to 800 '&post_id=' + this.options.post_id + // the post id 801 '&show_autosaves=' + this.options.showAutosaves + // show or hide autosaves 802 '&show_split_view=' + this.options.showSplitView + // show in split view or single column view 803 '&right_handle_at=' + this.options.rightHandleAt + // mark point for comparison list 804 '&left_handle_at=' + this.options.leftHandleAt + // mark point for comparison list 805 '&nonce=' + this.options.nonce; 806 }, 807 808 reload: function( options ) { 809 this.options = _.defaults( options.options || {}, this.options ); 810 811 this.fetch({ 812 success: options.success || null, 813 error: options.error || null 814 }); 815 } 816 817 } ); 818 819 $( wp.revisions ); 820 432 $( revisions.init ); 821 433 }(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 ); … … 77 83 $parent_file = $submenu_file = 'edit.php'; 78 84 79 85 wp_enqueue_script( 'revisions' ); 86 wp_localize_script( 'revisions', '_wpRevisionsSettings', wp_prepare_revisions_for_js( $post, $revision_id ) ); 80 87 81 82 $settings = array(83 'post_id' => $post->ID,84 'nonce' => wp_create_nonce( 'revisions-ajax-nonce' ),85 'revision_id' => $revision_id86 );87 88 wp_localize_script( 'revisions', 'wpRevisionsSettings', $settings );89 90 88 /* Revisions Help Tab */ 91 89 92 90 $revisions_overview = '<p>' . __( 'This screen is used for managing your content revisions.' ) . '</p>'; … … 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>133 139 140 <div class="revisions-slider"></div> 141 </script> 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" /> … … 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 165 <script id="tmpl-revision-interact " type="text/html">179 <script id="tmpl-revision-interact-old" type="text/html"> 166 180 <div id="diff-previous-revision"> 167 181 <input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" /> 168 182 </div> … … 171 185 <input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" /> 172 186 </div> 173 187 174 <div id="diff-slider" class="wp-slider"></div>175 188 </script> 176 189 177 190 <script id="tmpl-revision-ticks" type="text/html"> -
wp-admin/css/wp-admin.css
3481 3481 /*------------------------------------------------------------------------------ 3482 3482 11.2 - Post Revisions 3483 3483 ------------------------------------------------------------------------------*/ 3484 .revisions .spinner { 3485 float: none; 3486 margin: 100px auto; 3487 } 3484 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 .wp-slider { 3520 width: 70%; 3521 margin: 6px auto 0; 3522 } 3523 3485 3524 /* Revision meta box */ 3486 3525 .post-revisions li img, 3487 3526 #revisions-meta-restored img { … … 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; … … 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 }