From 77abb9ff01e7d9414010c5488663f90f185e24d9 Mon Sep 17 00:00:00 2001 From: Mark Jaquith Date: Wed, 26 Jun 2013 21:06:50 +0000 Subject: [PATCH] Cleanup of the revisions screen, both on the PHP API side, and the JS. * Much simpler PHP API * Cleaner and more Backbone-y JS API * Consequently, does batch queries; this now scales up to hundreds of revisions Currently missing, but much easier considering the cleaned up base: * Compare two mode * RTL props koopersmith, nacin, adamsilverstein, ocean90. see #24425 git-svn-id: http://core.svn.wordpress.org/trunk@24520 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/admin-ajax.php | 4 +- wp-admin/css/wp-admin.css | 70 +- wp-admin/includes/ajax-actions.php | 226 +----- wp-admin/includes/revision.php | 100 +++ wp-admin/js/revisions.js | 1158 ++++++++++------------------ wp-admin/revision.php | 101 ++- wp-includes/revision.php | 65 -- 7 files changed, 654 insertions(+), 1070 deletions(-) create mode 100644 wp-admin/includes/revision.php diff --git a/wp-admin/admin-ajax.php b/wp-admin/admin-ajax.php index ebe7550dc6..c638d78aad 100644 --- a/wp-admin/admin-ajax.php +++ b/wp-admin/admin-ajax.php @@ -42,7 +42,7 @@ do_action( 'admin_init' ); $core_actions_get = array( 'fetch-list', 'ajax-tag-search', 'wp-compression-test', 'imgedit-preview', 'oembed-cache', - 'autocomplete-user', 'dashboard-widgets', 'logged-in', 'revisions-data' + 'autocomplete-user', 'dashboard-widgets', 'logged-in', ); $core_actions_post = array( @@ -56,7 +56,7 @@ $core_actions_post = array( 'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post', 'wp-remove-post-lock', 'dismiss-wp-pointer', 'upload-attachment', 'get-attachment', 'query-attachments', 'save-attachment', 'save-attachment-compat', 'send-link-to-editor', - 'send-attachment-to-editor', 'save-attachment-order', 'heartbeat', + 'send-attachment-to-editor', 'save-attachment-order', 'heartbeat', 'get-revision-diffs', ); // Register core Ajax calls. diff --git a/wp-admin/css/wp-admin.css b/wp-admin/css/wp-admin.css index 7a0da1db0b..668cf2c6c1 100644 --- a/wp-admin/css/wp-admin.css +++ b/wp-admin/css/wp-admin.css @@ -3481,6 +3481,48 @@ td.plugin-title p { /*------------------------------------------------------------------------------ 11.2 - Post Revisions ------------------------------------------------------------------------------*/ +.revisions .spinner { + float: none; + margin: 100px auto; +} + +.revisions.loading .spinner { + display: block; +} + +.revisions-control-frame, +.revisions-diff-frame { + position: relative; +} + +.revisions-controls { + height: 60px; + padding: 40px 0 20px; + border-bottom: 1px solid #dfdfdf; + margin-bottom: 10px; +} + +.revisions-meta { + margin-top: 15px; +} +.revision-toggle-compare-mode { + position: absolute; + top: 0; + right: 0; +} + +.revisions-previous { + float: left; +} + +.revisions-next { + float: right; +} + +.wp-slider { + width: 70%; + margin: 6px auto 0; +} /* Revision meta box */ .post-revisions li img, @@ -3527,13 +3569,6 @@ table.diff .diff-addedline ins { position: relative; } -#toggle-revision-compare-mode { - position: absolute; - top: 0; - right: 0; - padding: 9px 9px 0 0; -} - #loading-status { display: none; position: absolute; @@ -3551,24 +3586,6 @@ table.diff .diff-addedline ins { padding: 20px 0; } -#diff-next-revision, -#diff-previous-revision { - margin-top: -.4em; /* Same line as the slider (height: .8em) */ -} - -#diff-next-revision { - float: right; -} - -#diff-previous-revision { - float: left; -} - -#diff-slider { - width: 70%; - margin: 0 auto; -} - .comparetwo #diff-slider { width: 95%; } @@ -3588,7 +3605,7 @@ table.diff .diff-addedline ins { } #diff-title-to strong { - display: none; + display: inline; } .comparing-two-revisions #diff-title-to strong { @@ -3605,6 +3622,7 @@ table.diff .diff-addedline ins { -webkit-border-radius: 3px; border-radius: 3px; padding: 5px 200px 5px 5px; + clear: both; } .diff-header { diff --git a/wp-admin/includes/ajax-actions.php b/wp-admin/includes/ajax-actions.php index 13b62628cb..257195018f 100644 --- a/wp-admin/includes/ajax-actions.php +++ b/wp-admin/includes/ajax-actions.php @@ -2082,219 +2082,31 @@ function wp_ajax_heartbeat() { wp_send_json($response); } -function wp_ajax_revisions_data() { - check_ajax_referer( 'revisions-ajax-nonce', 'nonce' ); +function wp_ajax_get_revision_diffs() { + require ABSPATH . 'wp-admin/includes/revision.php'; - $compare_to = ! empty( $_GET['compare_to'] ) ? absint( $_GET['compare_to'] ) : 0; - $show_autosaves = ! empty( $_GET['show_autosaves'] ); - $show_split_view = ! empty( $_GET['show_split_view'] ); - $post_id = ! empty( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0; - $right_handle_at = ! empty( $_GET['right_handle_at'] ) ? (int) $_GET['right_handle_at'] : 0; - $left_handle_at = ! empty( $_GET['left_handle_at'] ) ? (int) $_GET['left_handle_at'] : 0; - $single_revision_id = ! empty( $_GET['single_revision_id'] ) ? absint( $_GET['single_revision_id'] ) : 0; - $compare_two_mode = (bool) $post_id; + // check_ajax_referer( 'revisions-ajax-nonce', 'nonce' ); - $all_the_revisions = array(); - if ( ! $post_id ) - $post_id = $compare_to; + if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) ) + wp_send_json_error(); - if ( ! current_user_can( 'read_post', $post_id ) ) - continue; + if ( ! current_user_can( 'read_post', $post->ID ) ) + wp_send_json_error(); - if ( ! $revisions = wp_get_post_revisions( $post_id ) ) - return; + // Really just pre-loading the cache here. + if ( ! $revisions = wp_get_post_revisions( $post->ID ) ) + wp_send_json_error(); - $left_revision = get_post( $compare_to ); + $return = array(); + @set_time_limit( count( $_REQUEST['compare'] ) ); - // single model fetch mode - // return the diff of a single revision comparison - if ( $single_revision_id ) { - $right_revision = get_post( $single_revision_id ); + foreach ( $_REQUEST['compare'] as $compare_key ) { + list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to - if ( ! $compare_to ) - $left_revision = get_post( $post_id ); - - // make sure the right revision is the most recent, except on oldest revision - if ( $compare_to && $right_revision->post_date < $left_revision->post_date ) { - $temp = $left_revision; - $left_revision = $right_revision; - $right_revision = $temp; - } - - $lines_added = $lines_deleted = 0; - $content = ''; - // compare from left to right, passed from application - foreach ( _wp_post_revision_fields() as $field => $field_value ) { - $left_content = apply_filters( "_wp_post_revision_field_$field", $left_revision->$field, $field, $left_revision, 'left' ); - $right_content = apply_filters( "_wp_post_revision_field_$field", $right_revision->$field, $field, $right_revision, 'right' ); - - add_filter( "_wp_post_revision_field_$field", 'htmlspecialchars' ); - - $args = array(); - - if ( $show_split_view ) - $args = array( 'show_split_view' => true ); - - // compare_to == 0 means first revision, so compare to a blank field to show whats changed - $diff = wp_text_diff_with_count( ( 0 == $compare_to ) ? '' : $left_content, $right_content, $args ); - - if ( isset( $diff[ 'html' ] ) ) { - $content .= sprintf( '
%s
', $field_value ); - $content .= $diff[ 'html' ]; - } - - if ( isset( $diff[ 'lines_added' ] ) ) - $lines_added = $lines_added + $diff[ 'lines_added' ]; - - if ( isset( $diff[ 'lines_deleted' ] ) ) - $lines_deleted = $lines_deleted + $diff[ 'lines_deleted' ]; - } - $content = '' == $content ? __( 'No difference' ) : $content; - - $all_the_revisions = array ( - 'diff' => $content, - 'linesDeleted' => $lines_deleted, - 'linesAdded' => $lines_added + $return[] = array( + 'id' => $compare_key, + 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ), ); - - echo json_encode( $all_the_revisions ); - exit(); - } // end single model fetch - - $count = -1; - - // reverse the list to start with oldest revision - $revisions = array_reverse( $revisions ); - - $previous_revision_id = 0; - - /* translators: revision date format, see http://php.net/date */ - $datef = _x( 'j F, Y @ G:i:s', 'revision date format'); - - foreach ( $revisions as $revision ) : - if ( ! $show_autosaves && wp_is_post_autosave( $revision ) ) - continue; - - $revision_from_date_author = ''; - $is_current_revision = false; - $count++; - - /** - * return blank data for diffs to the left of the left handle (for right handel model) - * or to the right of the right handle (for left handel model) - * and visa versa in RTL mode - */ - if( ! is_rtl() ) { - if ( ( ( 0 != $left_handle_at && $count < $left_handle_at ) || - ( 0 != $right_handle_at && $count > ( $right_handle_at - 2 ) ) ) ) { - $all_the_revisions[] = array ( - 'ID' => $revision->ID, - ); - continue; - } - } else { // is_rtl - if ( ( 0 != $left_handle_at && $count > ( $left_handle_at - 1 ) || - ( 0 != $left_handle_at && $count < $right_handle_at ) ) ) { - $all_the_revisions[] = array ( - 'ID' => $revision->ID, - ); - continue; - } - } - - if ( $compare_two_mode ) { - $compare_to_gravatar = get_avatar( $left_revision->post_author, 24 ); - $compare_to_author = get_the_author_meta( 'display_name', $left_revision->post_author ); - $compare_to_date = date_i18n( $datef, strtotime( $left_revision->post_modified ) ); - - $revision_from_date_author = sprintf( - /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */ - _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ), - $compare_to_gravatar, - $compare_to_author, - human_time_diff( strtotime( $left_revision->post_modified ), current_time( 'timestamp' ) ), - $compare_to_date - ); - } - - $gravatar = get_avatar( $revision->post_author, 24 ); - $author = get_the_author_meta( 'display_name', $revision->post_author ); - $date = date_i18n( $datef, strtotime( $revision->post_modified ) ); - $revision_date_author = sprintf( - /* translators: post revision title: 1: author avatar, 2: author name, 3: time ago, 4: date */ - _x( '%1$s %2$s, %3$s ago (%4$s)', 'post revision title' ), - $gravatar, - $author, - human_time_diff( strtotime( $revision->post_modified ), current_time( 'timestamp' ) ), - $date - ); - - /* translators: 1: date */ - $autosavef = _x( '%1$s [Autosave]', 'post revision title extra' ); - /* translators: 1: date */ - $currentf = _x( '%1$s [Current Revision]', 'post revision title extra' ); - - if ( ! $post = get_post( $post_id ) ) - continue; - - if ( $left_revision->post_modified === $post->post_modified ) - $revision_from_date_author = sprintf( $currentf, $revision_from_date_author ); - elseif ( wp_is_post_autosave( $left_revision ) ) - $revision_from_date_author = sprintf( $autosavef, $revision_from_date_author ); - - if ( $revision->post_modified === $post->post_modified ) { - $revision_date_author = sprintf( $currentf, $revision_date_author ); - $is_current_revision = true; - } elseif ( wp_is_post_autosave( $revision ) ) { - $revision_date_author = sprintf( $autosavef, $revision_date_author ); - } - - /* translators: revision date short format, see http://php.net/date */ - $date_short_format = _x( 'j M @ G:i', 'revision date short format'); - $date_short = date_i18n( $date_short_format, strtotime( $revision->post_modified ) ); - - $revision_date_author_short = sprintf( - '%s %s
%s', - $gravatar, - $author, - $date_short - ); - - $restore_link = wp_nonce_url( - add_query_arg( - array( 'revision' => $revision->ID, - 'action' => 'restore' ), - admin_url( 'revision.php' ) - ), - "restore-post_{$revision->ID}" - ); - - // if this is a left handled calculation swap data - if ( 0 != $right_handle_at ) { - $tmp = $revision_from_date_author; - $revision_from_date_author = $revision_date_author; - $revision_date_author = $tmp; - } - - if ( ( $compare_two_mode || -1 !== $previous_revision_id ) ) { - $all_the_revisions[] = array ( - 'ID' => $revision->ID, - 'titleTo' => $revision_date_author, - 'titleFrom' => $revision_from_date_author, - 'titleTooltip' => $revision_date_author_short, - 'restoreLink' => urldecode( $restore_link ), - 'previousID' => $previous_revision_id, - 'isCurrent' => $is_current_revision, - ); - } - $previous_revision_id = $revision->ID; - - endforeach; - - // in RTL + single handle mode, reverse the revision direction - if ( is_rtl() && $compare_two_mode ) - $all_the_revisions = array_reverse( $all_the_revisions ); - - echo json_encode( $all_the_revisions ); - exit(); + } + wp_send_json_success( $return ); } diff --git a/wp-admin/includes/revision.php b/wp-admin/includes/revision.php new file mode 100644 index 0000000000..8510924112 --- /dev/null +++ b/wp-admin/includes/revision.php @@ -0,0 +1,100 @@ +post_parent !== $post->ID ) + return false; + if ( $compare_to->post_parent !== $post->ID ) + return false; + + if ( $compare_from && strtotime( $compare_from->post_date_gmt ) > strtotime( $compare_to->post_date_gmt ) ) { + $temp = $compare_from; + $compare_from = $compare_to; + $compare_to = $temp; + } + + $return = array(); + + foreach ( _wp_post_revision_fields() as $field => $name ) { + $content_from = $compare_from ? apply_filters( "_wp_post_revision_field_$field", $compare_from->$field, $field, $compare_from, 'left' ) : ''; + $content_to = apply_filters( "_wp_post_revision_field_$field", $compare_to->$field, $field, $compare_to, 'right' ); + + $diff = wp_text_diff( $content_from, $content_to, array( 'show_split_view' => true ) ); + + if ( ! $diff && 'post_title' === $field ) { + // It's a better user experience to still show the Title, even if it didn't change. + // No, you didn't see this. + $diff = ""; + $diff .= ''; + $diff .= ''; + $diff .= '
' . esc_html( $compare_from->post_title ) . '' . esc_html( $compare_to->post_title ) . '
'; + } + + if ( $diff ) { + $return[] = array( + 'id' => $field, + 'name' => $name, + 'diff' => $diff, + ); + } + } + return $return; +} + +function wp_prepare_revisions_for_js( $post, $selected_revision_id ) { + $post = get_post( $post ); + $revisions = array(); + $current = current_time( 'timestamp' ); + + $revisions = wp_get_post_revisions( $post->ID ); + + cache_users( wp_list_pluck( $revisions, 'post_author' ) ); + + foreach ( $revisions as $revision ) { + $modified_gmt = strtotime( $revision->post_modified_gmt ); + $restore_link = wp_nonce_url( + add_query_arg( + array( 'revision' => $revision->ID, + 'action' => 'restore' ), + admin_url( 'revision.php' ) + ), + "restore-post_{$revision->ID}" + ); + $revisions[ $revision->ID ] = array( + 'id' => $revision->ID, + 'title' => get_the_title( $post->ID ), + 'author' => array( + 'id' => (int) $revision->post_author, + 'avatar' => get_avatar( $revision->post_author, 24 ), + 'name' => get_the_author_meta( 'display_name', $revision->post_author ), + ), + 'date' => date_i18n( __( 'M j, Y @ G:i' ), $modified_gmt ), + 'dateShort' => date_i18n( _x( 'j M @ G:i', 'revision date short format' ), $modified_gmt ), + 'timeAgo' => human_time_diff( $modified_gmt, $current ), + 'autosave' => wp_is_post_autosave( $revision ), + 'current' => $revision->post_modified_gmt === $post->post_modified_gmt, + 'restoreUrl' => urldecode( $restore_link ), + ); + } + + return array( + 'postId' => $post->ID, + 'nonce' => wp_create_nonce( 'revisions-ajax-nonce' ), + 'revisionData' => array_values( $revisions ), + 'selectedRevision' => $selected_revision_id, + ); +} \ No newline at end of file diff --git a/wp-admin/js/revisions.js b/wp-admin/js/revisions.js index ce3f098244..67f7f80b8d 100644 --- a/wp-admin/js/revisions.js +++ b/wp-admin/js/revisions.js @@ -1,283 +1,207 @@ window.wp = window.wp || {}; (function($) { - var Revision, Revisions, Diff, revisions; + var revisions; - revisions = wp.revisions = function() { - Diff = revisions.Diff = new Diff(); - }; - - _.extend( revisions, { model: {}, view: {}, controller: {} } ); + revisions = wp.revisions = { model: {}, view: {}, controller: {} }; // Link settings. - revisions.model.settings = typeof wpRevisionsSettings === 'undefined' ? {} : wpRevisionsSettings; + revisions.settings = typeof _wpRevisionsSettings === 'undefined' ? {} : _wpRevisionsSettings; /** * ======================================================================== - * CONTROLLERS + * MODELS * ======================================================================== */ + revisions.model.Slider = Backbone.Model.extend({ + defaults: { + value: 0, + min: 0, + max: 1, + step: 1 + } + }); - /** - * wp.revisions.controller.Diff - * - * Controlls the diff - */ - Diff = revisions.controller.Diff = Backbone.Model.extend( { - rightDiff: 1, - leftDiff: 1, - revisions: null, - leftHandleRevisions: null, - rightHandleRevisions: null, - revisionsInteractions: null, - autosaves: true, - showSplitView: true, - singleRevision: true, - leftModelLoading: false, // keep track of model loads - rightModelLoading: false, // disallow slider interaction, also repeat loads, while loading - tickmarkView: null, // the slider tickmarks - slider: null, // the slider instance + revisions.model.Revision = Backbone.Model.extend({}); - constructor: function() { - var self = this; - this.slider = new revisions.view.Slider(); + revisions.model.Revisions = Backbone.Collection.extend({ + model: revisions.model.Revision, - if ( null === this.revisions ) { - this.revisions = new Revisions(); // set up collection - this.startRightModelLoading(); + comparator: function( revision ) { + return revision.id; + }, + }); - this.revisions.fetch({ // load revision data - success: function() { - self.stopRightModelLoading(); - self.completeApplicationSetup(); + revisions.model.Field = Backbone.Model.extend({}); + + revisions.model.Fields = Backbone.Collection.extend({ + model: revisions.model.Field + }); + + revisions.model.Diff = Backbone.Model.extend({ + initialize: function(attributes, options) { + var fields = this.get('fields'); + this.unset('fields'); + + this.fields = new revisions.model.Fields( fields ); + } + }); + + revisions.model.Diffs = Backbone.Collection.extend({ + initialize: function(models, options) { + this.revisions = options.revisions; + this.requests = {}; + }, + + model: revisions.model.Diff, + + ensure: function( id, context ) { + var diff = this.get( id ); + var request = this.requests[ id ]; + var deferred = $.Deferred(); + var ids = {}; + + if ( diff ) { + deferred.resolveWith( context, [ diff ] ); + } else { + this.trigger( 'ensure:load', ids ); + _.each( ids, _.bind( function(id) { + // Remove anything that has an ongoing request + if ( this.requests[ id ] ) + delete ids[ id ]; + }, this ) ); + if ( ! request ) { + // Always include the ID that started this ensure + ids[ id ] = true; + request = this.load( _.keys( ids ) ); + } + + request.done( _.bind( function() { + deferred.resolveWith( context, [ this.get( id ) ] ); + }, this ) ); + } + + return deferred.promise(); + }, + + loadNew: function( comparisons ) { + comparisons = _.object( comparisons, comparisons ); + _.each( comparisons, _.bind( function( id ) { + // Exists + if ( this.get( id ) ) + delete comparisons[ id ]; + }, this ) ); + comparisons = _.toArray( comparisons ); + return this.load( comparisons ); + }, + + load: function( comparisons ) { + // Our collection should only ever grow, never shrink, so remove: false + return this.fetch({ data: { compare: comparisons }, remove: false }); + }, + +/**/ + loadLast: function( num ) { + num = num || 1; + var ids = this.getProximalDiffIds(); + ids = _.last( ids, num ); + + if ( ids.length ) { + return this.loadNew( ids ); + } + }, + + loadLastUnloaded: function( num ) { + num = num || 1; + var ids = this.getUnloadedProximalDiffIds(); + ids = _.last( ids, num ); + + if ( ids.length ) { + return this.loadNew( ids ); + } + }, + + getProximalDiffIds: function() { + var previous = 0, ids = []; + this.revisions.each( _.bind( function(revision) { + ids.push( previous + ':' + revision.id ); + previous = revision.id; + }, this ) ); + return ids; + }, + + getUnloadedProximalDiffIds: function() { + var comparisons = this.getProximalDiffIds(); + comparisons = _.object( comparisons, comparisons ); + _.each( comparisons, _.bind( function( id ) { + // Exists + if ( this.get( id ) ) + delete comparisons[ id ]; + }, this ) ); + return _.toArray( comparisons ); + }, + + loadAllBy: function( chunkSize ) { + chunkSize = chunkSize || 20; + var unloaded = this.getUnloadedProximalDiffIds(); + if ( unloaded.length ) { + return this.loadLastUnloaded( chunkSize ).always( _.bind( function() { + this.loadAllBy( chunkSize ); + }, this ) ); + } + }, + + sync: function( method, model, options ) { + if ( 'read' === method ) { + options = options || {}; + options.context = this; + options.data = _.extend( options.data || {}, { + action: 'get-revision-diffs', + post_id: revisions.settings.postId + }); + + var deferred = wp.xhr.send( options ); + var requests = this.requests; + + // Record that we're requesting each diff. + if ( options.data.compare ) { + _.each( options.data.compare, function( id ) { + requests[ id ] = deferred; + }); + } + + // When the request completes, clear the stored request. + deferred.always( function() { + if ( options.data.compare ) { + _.each( options.data.compare, function( id ) { + delete requests[ id ]; + }); } }); - } - }, - loadDiffs: function( models ) { - var self = this, - revisionsToLoad = models.where( { completed: false } ), - delay = 0, - totalChanges; + return deferred; - // match slider to passed revision_id - _.each( revisionsToLoad, function( revision ) { - if ( revision.get( 'ID' ) == revisions.model.settings.revision_id ) - self.rightDiff = self.revisions.indexOf( revision ) + 1; - }); - - _.each( revisionsToLoad, function( revision ) { - _.delay( function() { - revision.fetch( { - update: true, - add: false, - remove: false, - success: function( model ) { - model.set( 'completed', true ); - - // stop spinner when all models are loaded - if ( 0 === models.where( { completed: false } ).length ) - self.stopModelLoadingSpinner(); - - totalChanges = model.get( 'linesAdded' ) + model.get( 'linesDeleted' ), - scopeOfChanges = 'vsmall'; - - // Note: hard coded scope of changes - // TODO change to dynamic based on range of values - if ( totalChanges > 1 && totalChanges <= 3 ) { - scopeOfChanges = 'small'; - } else if ( totalChanges > 3 && totalChanges <= 5 ) { - scopeOfChanges = 'med'; - } else if ( totalChanges > 5 && totalChanges <= 10 ) { - scopeOfChanges = 'large'; - } else if ( totalChanges > 10 ) { - scopeOfChanges = 'vlarge'; - } - model.set( 'scopeOfChanges', scopeOfChanges ); - if ( 0 !== self.rightDiff && - model.get( 'ID' ) === self.revisions.at( self.rightDiff - 1 ).get( 'ID' ) ) { - // reload if current model refreshed - self.revisionView.render(); - } - self.tickmarkView.render(); - } - } ); - }, delay ) ; - delay = delay + 150; // stagger model loads to avoid hammering server with requests - } - ); - }, - - startLeftModelLoading: function() { - this.leftModelLoading = true; - $('#revision-diff-container').addClass('left-model-loading'); - }, - - stopLeftModelLoading: function() { - this.leftModelLoading = false; - }, - - startRightModelLoading: function() { - this.rightModelLoading = true; - $('#revision-diff-container').addClass('right-model-loading'); - }, - - stopRightModelLoading: function() { - this.rightModelLoading = false; - }, - - stopModelLoadingSpinner: function() { - $('#revision-diff-container').removeClass('right-model-loading'); - $('#revision-diff-container').removeClass('left-model-loading'); - }, - - reloadModel: function() { - if ( this.singleRevision ) { - this.reloadModelSingle(); + // Otherwise, fall back to `Backbone.sync()`. } else { - this.reloadLeftRight(); + return Backbone.Model.prototype.sync.apply( this, arguments ); } + } + }); + + + revisions.model.FrameState = Backbone.Model.extend({ + initialize: function( attributes, options ) { + this.revisions = options.revisions; + this.diffs = new revisions.model.Diffs( [], {revisions: this.revisions} ); + + this.listenTo( this, 'change:from change:to', this.updateDiffId ); }, - // load the models for the single handle mode - reloadModelSingle: function() { - var self = this; - - self.startRightModelLoading(); - - self.revisions.reload({ - options: { - 'showAutosaves': self.autosaves, - 'showSplitView': self.showSplitView - }, - - success: function() { - var revisionCount = self.revisions.length; - self.revisionView.model = self.revisions; - self.revisionView.render(); - self.loadDiffs( self.revisions ); - self.tickmarkView.model = self.revisions; - self.tickmarkView.render(); - self.slider.refresh({ - 'max': revisionCount - 1, // slider starts at 0 in single handle mode - 'value': self.rightDiff - 1 // slider starts at 0 in single handle mode - }, true); - }, - - error: function() { - self.stopRightModelLoading(); - } - }); - }, - - // load the models for the left handle (the right handler has moved) - reloadLeft: function() { - var self = this; - self.startLeftModelLoading(); - self.leftHandleRevisions = new Revisions( {}, { - 'compareTo': self.revisions.at( self.rightDiff - 1 ).get( 'ID' ), // diff and model count off by 1 - 'showAutosaves': self.autosaves, - 'showSplitView': self.showSplitView, - 'rightHandleAt': self.rightDiff - }); - - self.leftHandleRevisions.fetch({ - success: function(){ - self.stopLeftModelLoading(); - self.loadDiffs( self.leftHandleRevisions ); - self.tickmarkView.model = self.leftHandleRevisions; - self.slider.refresh({ - 'max': self.revisions.length - }); - // ensure right handle not beyond length - if ( self.rightDiff > self.revisions.length ) - self.rightDiff = self.revisions.length; - }, - - error: function() { - self.stopLeftModelLoading(); - } - }); - }, - - // load the models for the right handle (the left handle has moved) - reloadRight: function() { - var self = this; - self.startRightModelLoading(); - self.rightHandleRevisions = new Revisions( {}, { - 'compareTo': self.revisions.at( self.leftDiff - 1 ).get( 'ID' ), // diff and model count off by 1 - 'showAutosaves': self.autosaves, - 'showSplitView': self.showSplitView, - 'leftHandleAt': self.leftDiff - }); - - self.rightHandleRevisions.fetch({ - success: function(){ - self.stopRightModelLoading(); - self.loadDiffs( self.rightHandleRevisions ); - self.tickmarkView.model = self.rightHandleRevisions; - self.slider.refresh({ - 'max': self.revisions.length - }, true); - }, - - error: function( response ) { - self.stopRightModelLoading(); - } - }); - - }, - - /** - * reloadLeftRight reload models for both the left and right handles - */ - reloadLeftRight: function() { - this.startRightModelLoading(); - this.startLeftModelLoading(); - this.reloadLeft(); - this.reloadRight(); - }, - - disabledButtonCheck: function( val ) { - var maxVal = this.revisions.length - 1, - next = ! isRtl ? $( '#next' ) : $( '#previous' ), - prev = ! isRtl ? $( '#previous' ) : $( '#next' ); - - // Disable "Next" button if you're on the last node - if ( maxVal === val ) - next.prop( 'disabled', true ); - else - next.prop( 'disabled', false ); - - // Disable "Previous" button if you're on the 0 node - if ( 0 === val ) - prev.prop( 'disabled', true ); - else - prev.prop( 'disabled', false ); - }, - - /** - * completeApplicationSetup finishes loading all views once the initial model load is complete - */ - completeApplicationSetup: function() { - this.revisionView = new revisions.view.Diff({ - model: this.revisions - }); - this.revisionView.render(); // render the revision view - - this.loadDiffs( this.revisions ); // get the actual revisions data - - this.revisionsInteractions = new revisions.view.Interact({ - model: this.revisions - }); - this.revisionsInteractions.render(); // render the interaction view - - this.tickmarkView = new revisions.view.Tickmarks({ - model: this.revisions - }); - this.tickmarkView.render(); // render the tickmark view + updateDiffId: function() { + var from = this.get( 'from' ); + var to = this.get( 'to' ); + this.set( 'diffId', (from ? from.id : '0') + ':' + to.id ); } }); @@ -288,534 +212,286 @@ window.wp = window.wp || {}; * ======================================================================== */ - /** - * wp.revisions.view.Slider - * - * The slider - */ - revisions.view.Slider = Backbone.View.extend({ - el: $( '#diff-slider' ), - singleRevision: true, + // The frame view. This contains the entire page. + revisions.view.Frame = wp.Backbone.View.extend({ + tagName: 'div', + className: 'revisions', + template: wp.template('revisions-frame'), - initialize: function( options ) { - this.options = _.defaults( options || {}, { - value: 0, - min: 0, - max: 1, - step: 1 + initialize: function() { + this.model = new revisions.model.FrameState({}, { + revisions: this.collection }); - }, - /** - * respond to slider slide events - * Note: in one handle mode, jQuery UI reports leftmost position as 0 - * in two handle mode, jQuery UI Slider reports leftmost position as 1 - */ - slide: function( event, ui ) { - if ( this.singleRevision ) { - Diff.rightDiff = ( ui.value + 1 ); - Diff.revisionView.render(); - Diff.disabledButtonCheck( ui.value ); - } else { - if ( ui.values[0] === ui.values[1] ) // prevent compare to self - return false; + this.listenTo( this.model, 'change:diffId', this.updateDiff ); - if ( $( ui.handle ).hasClass( 'left-handle' ) ) { - // Left handler - if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle - return false; + this.views.set( '.revisions-control-frame', new revisions.view.Controls({ + model: this.model + }) ); - Diff.leftDiff = isRtl ? ui.values[1] : ui.values[0]; // handles are reversed in RTL mode - } else { - // Right handler - if ( Diff.rightModelLoading ) // right model still loading, prevent sliding right handle - return false; + if ( this.model.revisions.length ) { + var last = this.model.revisions.last(2); + var attributes = { to: last.pop() }; - Diff.rightDiff = isRtl ? ui.values[0] : ui.values[1]; // handles are reversed in RTL mode - } + if ( last.length ) + attributes.from = last.pop(); - Diff.revisionView.render(); + this.model.set( attributes ); + + // Load the rest: first 10, then the rest by 50 + this.model.diffs.loadLastUnloaded( 10 ).always( _.bind( function() { + this.model.diffs.loadAllBy( 50 ); + }, this ) ); } }, - /** - * responds to slider start sliding events - * in two handle mode stores start position, so if unchanged at stop event no need to reload diffs - * also swaps in the appropriate models - left handled or right handled - */ - start: function( event, ui ) { - // Not needed in one mode - if ( this.singleRevision ) - return; - - if ( $( ui.handle ).hasClass( 'left-handle' ) ) { - // Left handler - if ( Diff.leftModelLoading ) // left model still loading, prevent sliding left handle - return false; - - Diff.revisionView.draggingLeft = true; - - if ( Diff.revisionView.model !== Diff.leftHandleRevisions && - null !== Diff.leftHandleRevisions ) { - Diff.revisionView.model = Diff.leftHandleRevisions; // use the left handle models - Diff.tickmarkView.model = Diff.leftHandleRevisions; - Diff.tickmarkView.render(); - } - - Diff.leftDiffStart = isRtl ? ui.values[1] : ui.values[0]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first - - } else { - // Right handler - if ( Diff.rightModelLoading || 0 === Diff.rightHandleRevisions.length) // right model still loading, prevent sliding right handle - return false; - - if ( Diff.revisionView.model !== Diff.rightHandleRevisions && - null !== Diff.rightHandleRevisions ) { - Diff.revisionView.model = Diff.rightHandleRevisions; // use the right handle models - Diff.tickmarkView.model = Diff.rightHandleRevisions; - Diff.tickmarkView.render(); - } - - Diff.revisionView.draggingLeft = false; - Diff.rightDiffStart = isRtl ? ui.values[0] : ui.values[1]; // in RTL mode the 'left handle' is the second in the slider, 'right' is first - } - }, - - /** - * responds to slider stop events - * in two handled mode, if the handle that stopped has moved, reload the diffs for the other handle - * the other handle compares to this handle's position, so if it changes they need to be recalculated - */ - stop: function( event, ui ) { - // Not needed in one mode - if ( this.singleRevision ) - return; - - // calculate and generate a diff for comparing to the left handle - // and the right handle, swap out when dragging - if ( $( ui.handle ).hasClass( 'left-handle' ) ) { - // Left handler - 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 - Diff.reloadRight(); - } else { - // Right handler - 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 - Diff.reloadLeft(); - } - }, - - addTooltip: function( handle, message ) { - handle.find( '.ui-slider-tooltip' ).html( message ); - }, - - width: function() { - return $( '#diff-slider' ).width(); - }, - - setWidth: function( width ) { - $( '#diff-slider' ).width( width ); - }, - - refresh: function( options, slide ) { - $( '#diff-slider' ).slider( 'option', options ); - - // Triggers the slide event - if ( slide ) - $( '#diff-slider' ).trigger( 'slide' ); - - Diff.disabledButtonCheck( options.value ); - }, - - option: function( key ) { - return $( '#diff-slider' ).slider( 'option', key ); - }, - render: function() { - var self = this; - // this.$el doesn't work, why? - $( '#diff-slider' ).slider( { - slide: $.proxy( self.slide, self ), - start: $.proxy( self.start, self ), - stop: $.proxy( self.stop, self ) - } ); + wp.Backbone.View.prototype.render.apply( this, arguments ); - // Set options - this.refresh( this.options ); - } - }); - - /** - * wp.revisions.view.Tickmarks - * - * The slider tickmarks. - */ - revisions.view.Tickmarks = Backbone.View.extend({ - el: $('#diff-slider-ticks'), - template: wp.template('revision-ticks'), - model: Revision, - - resetTicks: function() { - var sliderMax, sliderWidth, adjustMax, tickWidth, tickCount = 0, aTickWidth, tickMargin, self = this, firstTick, lastTick; - sliderMax = Diff.slider.option( 'max' ); - sliderWidth = Diff.slider.width(); - adjustMax = Diff.singleRevision ? 0 : 1; - tickWidth = Math.floor( sliderWidth / ( sliderMax - adjustMax ) ); - tickWidth = ( tickWidth > 50 ) ? 50 : tickWidth; // set minimum and maximum widths for tick marks - tickWidth = ( tickWidth < 6 ) ? 6 : tickWidth; - sliderWidth = tickWidth * ( sliderMax - adjustMax ); // calculate the slider width - aTickWidth = $( '.revision-tick' ).width(); - - if ( tickWidth !== aTickWidth ) { // is the width already set correctly? - $( '.revision-tick' ).each( function() { - tickMargin = Math.floor( ( tickWidth - $( this ).width() ) / 2 ) + 1; - $( this ).css( 'border-left', tickMargin + 'px solid #f7f7f7'); // space the ticks out using margins - $( this ).css( 'border-right', ( tickWidth - tickMargin - $( this ).width() ) + 'px solid #f7f7f7'); // space the ticks out using margins - }); - firstTick = $( '.revision-tick' ).first(); //cache selectors for optimization - lastTick = $( '.revision-tick' ).last(); - - sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( lastTick.outerWidth() - lastTick.innerWidth() ) ) / 2 ); // room for the last tick - sliderWidth = sliderWidth + Math.ceil( ( tickWidth - ( firstTick.outerWidth() - firstTick.innerWidth() ) ) / 2 ); // room for the first tick - firstTick.css( 'border-left', 'none' ); // first tick gets no left border - lastTick.css( 'border-right', 'none' ); // last tick gets no right border - } - - /** - * reset the slider width - */ - Diff.slider.setWidth( sliderWidth ); - $( '.diff-slider-ticks-wrapper' ).width( sliderWidth ); - $( '#diff-slider-ticks' ).width( sliderWidth ); - - /** - * go through all ticks, add hover and click interactions - */ - $( '.revision-tick' ).each( function() { - Diff.slider.addTooltip ( $( this ), Diff.revisions.at( tickCount++ ).get( 'titleTooltip' ) ); - $( this ).hover( - function() { - $( this ).find( '.ui-slider-tooltip' ).show().append('
'); - }, - function() { - $( this ).find( '.ui-slider-tooltip' ).hide().find( '.arrow' ).remove(); - } - ); - - /** - * move the slider handle when the tick marks are clicked - */ - $( this ).on( 'click', - { tickCount: tickCount }, // pass the tick through so we know where to move the handle - function( event ) { - if ( Diff.slider.singleRevision ) { // single handle mode - Diff.rightDiff = event.data.tickCount; // reposition the right handle - Diff.slider.refresh({ - value: Diff.rightDiff - 1 - } ); - } else { //compare two mode - if ( isRtl ) { - if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side - Diff.rightDiff = event.data.tickCount; // set the 'right' handle location - Diff.reloadLeft(); // reload the left handle comparison models - } else { // middle or 'right' clicks - Diff.leftDiff = event.data.tickCount; // set the 'left' handle location - Diff.reloadRight(); // reload right handle models - } - } else { - if ( event.data.tickCount < Diff.leftDiff ) { // click was on the 'left' side - Diff.leftDiff = event.data.tickCount; // set the left handle location - Diff.reloadRight(); // reload the right handle comparison models - } else { // middle or 'right' clicks - Diff.rightDiff = event.data.tickCount; // set the right handle location - Diff.reloadLeft(); // reload left handle models - } - } - Diff.slider.refresh( { // set the slider handle positions - values: [ isRtl ? Diff.rightDiff : Diff.leftDiff, isRtl ? Diff.leftDiff : Diff.rightDiff ] - } ); - } - Diff.revisionView.render(); // render the main view - } ); - } ); - }, - - // render the tick mark view - render: function() { - var self = this, addHtml; - - if ( null !== self.model ) { - addHtml = ""; - _.each ( self.model.models, function( theModel ) { - addHtml = addHtml + self.template ( theModel.toJSON() ); - }); - self.$el.html( addHtml ); - - } - self.resetTicks(); - return self; - } - } ); - - /** - * wp.revisions.view.Interact - * - * Next/Prev buttons and the slider - */ - revisions.view.Interact = Backbone.View.extend({ - el: $( '#revision-interact' ), - template: wp.template( 'revision-interact' ), - - // next and previous buttons, only available in compare one mode - events: { - 'click #next': ! isRtl ? 'nextRevision' : 'previousRevision', - 'click #previous': ! isRtl ? 'previousRevision' : 'nextRevision' - }, - - render: function() { - var modelcount; - this.$el.html( this.template ); - - modelcount = Diff.revisions.length; - - Diff.slider.singleRevision = Diff.singleRevision; - Diff.slider.render(); - - if ( Diff.singleRevision ) { - Diff.slider.refresh({ - value: Diff.rightDiff - 1, // rightDiff value is off model index by 1 - min: 0, - max: modelcount - 1 - }); - - $( '#revision-diff-container' ).removeClass( 'comparing-two-revisions' ); - - } else { - Diff.slider.refresh({ - // in RTL mode the 'left handle' is the second in the slider, 'right' is first - values: [ isRtl ? Diff.rightDiff : Diff.leftDiff, isRtl ? Diff.leftDiff : Diff.rightDiff ], - min: 1, - max: modelcount + 1, - range: true - }); - - $( '#revision-diff-container' ).addClass( 'comparing-two-revisions' ); - // in RTL mode the 'left handle' is the second in the slider, 'right' is first - $( '#diff-slider a.ui-slider-handle' ).first().addClass( isRtl ? 'right-handle' : 'left-handle' ); - $( '#diff-slider a.ui-slider-handle' ).last().addClass( isRtl ? 'left-handle' : 'right-handle' ); - - } + $('#wpbody-content .wrap').append( this.el ); + this.views.ready(); return this; }, - // go to the next revision + updateDiff: function() { + this.model.diffs.ensure( this.model.get('diffId'), this ).done( function( diff ) { + if ( this.model.get('diffId') !== diff.id ) + return; + this.views.set( '.revisions-diff-frame', new revisions.view.Diff({ + model: diff + })); + }); + } + }); + + // The control view. + // This contains the revision slider, previous/next buttons, and the compare checkbox. + revisions.view.Controls = wp.Backbone.View.extend({ + tagName: 'div', + className: 'revisions-controls', + + initialize: function() { + // Add the button view + this.views.add( new revisions.view.Buttons({ + model: this.model + })); + + // Add the Slider view + this.views.add( new revisions.view.Slider({ + model: this.model + }) ); + + // Add the Meta view + this.views.add( new revisions.view.Meta({ + model: this.model + }) ); + } + }); + + // The meta view. + // This contains the revision meta, and the restore button. + revisions.view.Meta = wp.Backbone.View.extend({ + tagName: 'div', + className: 'revisions-meta', + template: wp.template('revisions-meta'), + + initialize: function() { + this.listenTo( this.model, 'change:diffId', this.updateMeta ); + }, + + events: { + 'click #restore-revision': 'restoreRevision' + }, + + restoreRevision: function() { + var restoreUrl = this.model.get('to').attributes.restoreUrl.replace(/&/g, '&'); + document.location = restoreUrl; + }, + + updateMeta: function() { + this.$el.html( this.template( this.model.toJSON() ) ); + if( this.model.get( 'to' ).attributes.current ) { + $( '#restore-revision' ).prop( 'disabled', true); + } else { + $( '#restore-revision' ).prop( 'disabled', false) + } + } + }); + + + // The buttons view. + // Encapsulates all of the configuration for the previous/next buttons, and the compare checkbox. + revisions.view.Buttons = wp.Backbone.View.extend({ + tagName: 'div', + className: 'revisions-buttons', + template: wp.template('revisions-controls'), + + initialize: function() { + this.$el.html( this.template() ) + }, + + events: { + 'click #next': 'nextRevision', + 'click #previous': 'previousRevision' + }, + + gotoModel: function( toIndex ) { + var attributes = { + to: this.model.revisions.at( isRtl ? this.model.revisions.length - toIndex - 1 : toIndex ) // Reverse directions for Rtl + }; + // If we're at the first revision, unset 'from'. + if ( isRtl ? this.model.revisions.length - toIndex - 1 : toIndex ) // Reverse directions for Rtl + attributes.from = this.model.revisions.at( isRtl ? this.model.revisions.length - toIndex - 2 : toIndex - 1 ); + else + this.model.unset('from', { silent: true }); + + this.model.set( attributes ); + }, + nextRevision: function() { - if ( Diff.rightDiff < this.model.length ) // unless at right boundry - Diff.rightDiff = Diff.rightDiff + 1 ; - - Diff.revisionView.render(); - - Diff.slider.refresh({ - value: Diff.rightDiff - 1 - }, true ); + var toIndex = this.model.revisions.indexOf( this.model.get( 'to' ) ); + toIndex = isRtl ? toIndex - 1 : toIndex + 1; + this.gotoModel( toIndex ); }, - - // go to the previous revision + previousRevision: function() { - if ( Diff.rightDiff > 1 ) // unless at left boundry - Diff.rightDiff = Diff.rightDiff - 1 ; + var toIndex = this.model.revisions.indexOf( this.model.get('to') ); + toIndex = isRtl ? toIndex + 1 : toIndex - 1; + this.gotoModel( toIndex ); + }, + + ready: function() { + this.listenTo( this.model, 'change:diffId', this.disabledButtonCheck ); + }, + + // Check to see if the Previous or Next buttons need to be disabled or enabled + disabledButtonCheck: function() { + var maxVal = isRtl ? 0 : this.model.revisions.length - 1, + minVal = isRtl ? this.model.revisions.length - 1 : 0, + next = $( '.revisions-next .button' ), + previous = $( '.revisions-previous .button' ), + val = this.model.revisions.indexOf( this.model.get( 'to' ) ); + + // Disable "Next" button if you're on the last node + if ( maxVal === val ) + next.prop( 'disabled', true ); + else + next.prop( 'disabled', false ); + + // Disable "Previous" button if you're on the first node + if ( minVal === val ) + previous.prop( 'disabled', true ); + else + previous.prop( 'disabled', false ); + }, - Diff.revisionView.render(); - Diff.slider.refresh({ - value: Diff.rightDiff - 1 - }, true ); - } }); - /** - * wp.revisions.view.Diff - * - * Diff, compare two checkbox and restore button - */ - revisions.view.Diff = Backbone.View.extend({ - el: $( '#revisions-diff' ), - template: wp.template( 'revisions-diff' ), - draggingLeft: false, + // The slider view. + // Encapsulates all of the configuration for the jQuery UI slider into a view. + revisions.view.Slider = wp.Backbone.View.extend({ + tagName: 'div', + className: 'wp-slider', - // the compare two button is in this view, add the interaction here - events: { - 'click #compare-two-revisions': 'compareTwo', - 'click #restore-revision': 'restore' + initialize: function() { + _.bindAll( this, 'start', 'slide', 'stop' ); + + // Create the slider model from the provided collection data. + // TODO: This should actually pull from the model's `to` key. + var latestRevisionIndex = this.model.revisions.length - 1; + + // Find the initially selected revision + var initiallySelectedRevisionIndex = + this.model.revisions.indexOf( + this.model.revisions.findWhere( { id: Number( revisions.settings.selectedRevision ) } ) ); + + this.settings = new revisions.model.Slider({ + max: latestRevisionIndex, + value: initiallySelectedRevisionIndex, + start: this.start, + slide: this.slide, + stop: this.stop + }); }, - // render the revisions - render: function() { - var addHtml = '', thediff; + ready: function() { + this.$el.slider( this.settings.toJSON() ); + this.settings.on( 'change', function( model, options ) { + // Apply changes to slider settings here. + this.$el.slider( { value: this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); // Set handle to current to model + }, this ); + // Reset to the initially selected revision + this.slide( '', this.settings.attributes ); - // compare two revisions mode? - if ( ! Diff.singleRevision ) { - if ( this.draggingLeft ) { - thediff = Diff.leftDiff - 1; //leftDiff value is off model index by 1 - if ( this.model.at( thediff ) ) { - addHtml = this.template( this.model.at( thediff ).toJSON() ); - } - } else { // dragging right handle - thediff = Diff.rightDiff - 1; // rightDiff value is off model index by 1 - if ( this.model.at( thediff ) ) { - addHtml = this.template( this.model.at( thediff ).toJSON() ); - } - } - } else { // end compare two revisions mode, eg only one slider handle - if ( this.model.at( Diff.rightDiff - 1 ) ) { // rightDiff value is off model index by 1 - addHtml = this.template( this.model.at( Diff.rightDiff - 1 ).toJSON() ); - } - } - this.$el.html( addHtml ); + // Listen for changes in the diffId + this.listenTo( this.model, 'change:diffId', this.diffIdChanged ); - if ( this.model.length < 2 ) { - $( '#diff-slider' ).hide(); // don't allow compare two if fewer than three revisions - $( '.diff-slider-ticks-wrapper' ).hide(); - } - - this.toggleCompareTwoCheckbox(); - - // hide the restore button when on the last sport/current post data - $( '#restore-revision' ).toggle( ! Diff.revisions.at( Diff.rightDiff - 1 ).get( 'isCurrent' ) ); - - return this; }, - toggleCompareTwoCheckbox: function() { - // don't allow compare two if fewer than three revisions - if ( this.model.length < 3 ) - $( '#toggle-revision-compare-mode' ).hide(); - - $( '#compare-two-revisions' ).prop( 'checked', ! Diff.singleRevision ); + diffIdChanged: function() { + // Reset the view settings when diffId is changed + this.settings.set( { 'value': this.model.revisions.indexOf( this.model.get( 'to' ) ) } ); }, - // turn on/off the compare two mode - compareTwo: function() { - if ( $( '#compare-two-revisions' ).is( ':checked' ) ) { // compare 2 mode - Diff.singleRevision = false ; + start: function( event, ui ) { + // Track the mouse position to enable smooth dragging, overrides default jquery ui step behaviour + $( window ).mousemove( function( e ) { + var sliderLeft = $( '.wp-slider' ).offset().left, + sliderRight = sliderLeft + $( '.wp-slider' ).width(); - // in RTL mode handles are swapped, so boundary checks are different; - if ( isRtl ){ - Diff.leftDiff = Diff.revisions.length; // put the left handle at the rightmost position, representing current revision - - if ( Diff.revisions.length === Diff.rightDiff ) // make sure 'left' handle not in rightmost slot - Diff.rightDiff = Diff.rightDiff - 1; + // Follow mouse movements, as long as handle remains inside slider + if ( e.clientX < sliderLeft ) { + $( ui.handle ).css( 'left', 0 ); // Mouse to left of slider + } else if ( e.clientX > sliderRight ) { + $( ui.handle ).css( 'left', sliderRight - sliderLeft); // Mouse to right of slider } else { - if ( 1 === Diff.rightDiff ) // make sure right handle not in leftmost slot - Diff.rightDiff = 2; + $( ui.handle ).css( 'left', e.clientX - sliderLeft ); // Mouse in slider } - - Diff.revisionView.draggingLeft = false; - - revisions.model.settings.revision_id = ''; // reset passed revision id so switching back to one handle mode doesn't re-select revision - Diff.reloadLeftRight(); // load diffs for left and right handles - Diff.revisionView.model = Diff.rightHandleRevisions; - - } else { // compare one mode - Diff.singleRevision = true; - Diff.revisionView.draggingLeft = false; - Diff.reloadModelSingle(); - } - Diff.revisionsInteractions.render(); - Diff.tickmarkView.render(); + } ); // End mousemove }, - restore: function() { - document.location = $( '#restore-revision' ).data( 'restoreLink' ); + slide: function( event, ui ) { + var attributes = { + to: this.model.revisions.at( isRtl ? this.model.revisions.length - ui.value - 1 : ui.value ) // Reverse directions for Rtl + }; + + // If we're at the first revision, unset 'from'. + if ( isRtl ? this.model.revisions.length - ui.value - 1 : ui.value ) // Reverse directions for Rtl + attributes.from = this.model.revisions.at( isRtl ? this.model.revisions.length - ui.value - 2 : ui.value - 1 ); + else + this.model.unset('from', { silent: true }); + + this.model.set( attributes ); + }, + + stop: function( event, ui ) { + $( window ).unbind( 'mousemove' ); // Stop tracking the mouse + // Reset settings pops handle back to the step position + this.settings.trigger( 'change' ); } }); + // The diff view. + // This is the view for the current active diff. + revisions.view.Diff = wp.Backbone.View.extend({ + tagName: 'div', + className: 'revisions-diff', + template: wp.template('revisions-diff'), - /** - * ======================================================================== - * MODELS - * ======================================================================== - */ - - /** - * wp.revisions.Revision - */ - Revision = revisions.model.Revision = Backbone.Model.extend({ - idAttribute: 'ID', - - defaults: { - ID: 0, - titleTo: '', - titleTooltip: '', - titleFrom: '', - diff: '
', - restoreLink: '', - completed: false, - linesAdded: 0, - linesDeleted: 0, - scopeOfChanges: 'none', - previousID: 0, - isCurrent: false - }, - - url: function() { - if ( Diff.singleRevision ) { - return ajaxurl + - '?action=revisions-data' + - '&show_autosaves=true' + - '&show_split_view=true' + - '&nonce=' + revisions.model.settings.nonce + - '&single_revision_id=' + this.id + - '&compare_to=' + this.get( 'previousID' ) + - '&post_id=' + revisions.model.settings.post_id; - } else { - return this.collection.url() + '&single_revision_id=' + this.id; - } - + // Generate the options to be passed to the template. + prepare: function() { + return _.extend({ fields: this.model.fields.toJSON() }, this.options ); } }); - /** - * wp.revisions.Revisions - */ - Revisions = revisions.Revisions = Backbone.Collection.extend({ - model: Revision, - - initialize: function( models, options ) { - this.options = _.defaults( options || {}, { - 'compareTo': revisions.model.settings.post_id, - 'post_id': revisions.model.settings.post_id, - 'showAutosaves': true, - 'showSplitView': true, - 'rightHandleAt': 0, - 'leftHandleAt': 0, - 'nonce': revisions.model.settings.nonce - }); - }, - - url: function() { - return ajaxurl + - '?action=revisions-data' + - '&compare_to=' + this.options.compareTo + // revision are we comparing to - '&post_id=' + this.options.post_id + // the post id - '&show_autosaves=' + this.options.showAutosaves + // show or hide autosaves - '&show_split_view=' + this.options.showSplitView + // show in split view or single column view - '&right_handle_at=' + this.options.rightHandleAt + // mark point for comparison list - '&left_handle_at=' + this.options.leftHandleAt + // mark point for comparison list - '&nonce=' + this.options.nonce; - }, - - reload: function( options ) { - this.options = _.defaults( options.options || {}, this.options ); - - this.fetch({ - success: options.success || null, - error: options.error || null - }); - } - - } ); - - $( wp.revisions ); + // Initialize the revisions UI. + revisions.init = function() { + revisions.view.frame = new revisions.view.Frame({ + collection: new revisions.model.Revisions( revisions.settings.revisionData ) + }).render(); + }; + $( revisions.init ); }(jQuery)); diff --git a/wp-admin/revision.php b/wp-admin/revision.php index ec7ee51ca0..7c390989c6 100644 --- a/wp-admin/revision.php +++ b/wp-admin/revision.php @@ -8,6 +8,12 @@ /** WordPress Administration Bootstrap */ require_once('./admin.php'); + +require ABSPATH . 'wp-admin/includes/revision.php'; + +// wp_get_revision_ui_diff( $post, $compare_from, $compare_to ) +// wp_prepare_revisions_for_js( $post ) + wp_reset_vars( array( 'revision', 'action' ) ); $revision_id = absint( $revision ); @@ -21,7 +27,6 @@ case 'restore' : if ( ! current_user_can( 'edit_post', $revision->post_parent ) ) break; - if ( ! $post = get_post( $revision->post_parent ) ) break; @@ -77,15 +82,7 @@ else $parent_file = $submenu_file = 'edit.php'; wp_enqueue_script( 'revisions' ); - - -$settings = array( - 'post_id' => $post->ID, - 'nonce' => wp_create_nonce( 'revisions-ajax-nonce' ), - 'revision_id' => $revision_id -); - -wp_localize_script( 'revisions', 'wpRevisionsSettings', $settings ); +wp_localize_script( 'revisions', '_wpRevisionsSettings', wp_prepare_revisions_for_js( $post, $revision_id ) ); /* Revisions Help Tab */ @@ -114,24 +111,73 @@ require_once( './admin-header.php' );
-
-

- -
-

-
- -
-
-
- -
- -
-
+

+ + + + + + + + + -