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
This commit is contained in:
Mark Jaquith 2013-06-26 21:06:50 +00:00
parent b14622b2ff
commit 77abb9ff01
7 changed files with 654 additions and 1070 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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( '<div class="diff-label">%s</div>', $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 <strong>%s</strong><br />%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 );
}

View File

@ -0,0 +1,100 @@
<?php
function wp_get_revision_ui_diff( $post, $compare_from, $compare_to ) {
if ( ! $post = get_post( $post ) )
return false;
if ( $compare_from ) {
if ( ! $compare_from = get_post( $compare_from ) )
return false;
} else {
// If we're dealing with the first revision...
$compare_from = false;
}
if ( ! $compare_to = get_post( $compare_to ) )
return false;
// If comparing revisions, make sure we're dealing with the right post parent.
if ( $compare_from && $compare_from->post_parent !== $post->ID )
return false;
if ( $compare_to->post_parent !== $post->ID )
return false;
if ( $compare_from && strtotime( $compare_from->post_date_gmt ) > strtotime( $compare_to->post_date_gmt ) ) {
$temp = $compare_from;
$compare_from = $compare_to;
$compare_to = $temp;
}
$return = array();
foreach ( _wp_post_revision_fields() as $field => $name ) {
$content_from = $compare_from ? apply_filters( "_wp_post_revision_field_$field", $compare_from->$field, $field, $compare_from, 'left' ) : '';
$content_to = apply_filters( "_wp_post_revision_field_$field", $compare_to->$field, $field, $compare_to, 'right' );
$diff = wp_text_diff( $content_from, $content_to, array( 'show_split_view' => true ) );
if ( ! $diff && 'post_title' === $field ) {
// It's a better user experience to still show the Title, even if it didn't change.
// No, you didn't see this.
$diff = "<table class='diff'><col class='ltype' /><col class='content' /><col class='ltype' /><col class='content' /><tbody><tr>";
$diff .= '<td>' . esc_html( $compare_from->post_title ) . '</td><td></td><td>' . esc_html( $compare_to->post_title ) . '</td>';
$diff .= '</tr></tbody>';
$diff .= '</table>';
}
if ( $diff ) {
$return[] = array(
'id' => $field,
'name' => $name,
'diff' => $diff,
);
}
}
return $return;
}
function wp_prepare_revisions_for_js( $post, $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,
);
}

File diff suppressed because it is too large Load Diff

View File

@ -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' );
<div class="wrap">
<?php screen_icon(); ?>
<div id="revision-diff-container" class="current-version right-model-loading">
<h2 class="long-header"><?php echo $h2; ?></h2>
<div id="loading-status" class="updated message">
<p><span class="spinner" ></span></p>
</div>
<div class="diff-slider-ticks-wrapper">
<div id="diff-slider-ticks"></div>
</div>
<div id="revision-interact"></div>
<div id="revisions-diff"></div>
</div>
<h2 class="long-header"><?php echo $h2; ?></h2>
</div>
<script id="tmpl-revisions-frame" type="text/html">
<span class="spinner"></span>
<div class="revisions-control-frame"></div>
<div class="revisions-diff-frame"></div>
</script>
<script id="tmpl-revisions-controls" type="text/html">
<div class="revision-toggle-compare-mode">
<label>
<input type="checkbox" class="compare-two-revisions" />
<?php esc_attr_e( 'Compare two revisions' ); ?>
</label>
</div>
<div class="revisions-previous">
<input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" />
</div>
<div class="revisions-next">
<input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" />
</div>
</script>
<script id="tmpl-revisions-meta" type="text/html">
<div id="diff-header">
<div id="diff-header-from" class="diff-header">
<div id="diff-title-from" class="diff-title">
<strong>
<?php _ex( 'From:', 'Followed by post revision info' ); ?></strong>
<# if ( 'undefined' !== typeof data.from ) { #>
{{{ data.from.attributes.author.avatar }}} {{{ data.from.attributes.author.name }}},
{{{ data.from.attributes.timeAgo }}} <?php _e( 'ago' ); ?>
({{{ data.from.attributes.dateShort }}})
<# } #>
</div>
<div class="clear"></div>
</div>
<div id="diff-header-to" class="diff-header">
<div id="diff-title-to" class="diff-title">
<strong><?php _ex( 'To:', 'Followed by post revision info' ); ?></strong>
<# if ( 'undefined' !== typeof data.to ) { #>
{{{ data.to.attributes.author.avatar }}} {{{ data.to.attributes.author.name }}},
{{{ data.to.attributes.timeAgo }}} <?php _e( 'ago' ); ?>
({{{ data.to.attributes.dateShort }}})
<# } #>
</div>
<input type="button" id="restore-revision" class="button button-primary" data-restore-link="{{{ data.restoreLink }}}" value="<?php esc_attr_e( 'Restore This Revision' )?>" />
</div>
</div>
</script>
<script id="tmpl-revisions-diff" type="text/html">
<# _.each( data.fields, function( field ) { #>
<h3>{{{ field.name }}}</h3>
{{{ field.diff }}}
<# }); #>
</script>
<script id="tmpl-revisions-diff-old" type="text/html">
<div id="toggle-revision-compare-mode">
<label>
<input type="checkbox" id="compare-two-revisions" />
@ -157,12 +203,10 @@ require_once( './admin-header.php' );
</div>
</div>
</div>
<div id="diff-table">{{{ data.diff }}}</div>
</script>
<script id="tmpl-revision-interact" type="text/html">
<script id="tmpl-revision-interact-old" type="text/html">
<div id="diff-previous-revision">
<input class="button" type="button" id="previous" value="<?php echo esc_attr_x( 'Previous', 'Button label for a previous revision' ); ?>" />
</div>
@ -171,7 +215,6 @@ require_once( './admin-header.php' );
<input class="button" type="button" id="next" value="<?php echo esc_attr_x( 'Next', 'Button label for a next revision' ); ?>" />
</div>
<div id="diff-slider" class="wp-slider"></div>
</script>
<script id="tmpl-revision-ticks" type="text/html">

View File

@ -597,68 +597,3 @@ function _wp_upgrade_revisions_of_post( $post, $revisions ) {
return true;
}
/**
* Displays a human readable HTML representation of the difference between two strings.
* similar to wp_text_diff, but tracks and returns could of lines added and removed
*
* @since 3.6.0
*
* @see wp_parse_args() Used to change defaults to user defined settings.
* @uses Text_Diff
* @uses WP_Text_Diff_Renderer_Table
*
* @param string $left_string "old" (left) version of string
* @param string $right_string "new" (right) version of string
* @param string|array $args Optional. Change 'title', 'title_left', and 'title_right' defaults.
* @return array contains html, linesadded & linesdeletd, empty string if strings are equivalent.
*/
function wp_text_diff_with_count( $left_string, $right_string, $args = null ) {
$defaults = array( 'title' => '', 'title_left' => '', 'title_right' => '' );
$args = wp_parse_args( $args, $defaults );
if ( ! class_exists( 'WP_Text_Diff_Renderer_Table' ) )
require( ABSPATH . WPINC . '/wp-diff.php' );
$left_string = normalize_whitespace( $left_string );
$right_string = normalize_whitespace( $right_string );
$left_lines = explode( "\n", $left_string );
$right_lines = explode( "\n", $right_string) ;
$text_diff = new Text_Diff($left_lines, $right_lines );
$lines_added = $text_diff->countAddedLines();
$lines_deleted = $text_diff->countDeletedLines();
$renderer = new WP_Text_Diff_Renderer_Table();
$diff = $renderer->render( $text_diff );
if ( !$diff )
return '';
$r = "<table class='diff'>\n";
if ( ! empty( $args[ 'show_split_view' ] ) ) {
$r .= "<col class='content diffsplit left' /><col class='content diffsplit middle' /><col class='content diffsplit right' />";
} else {
$r .= "<col class='content' />";
}
if ( $args['title'] || $args['title_left'] || $args['title_right'] )
$r .= "<thead>";
if ( $args['title'] )
$r .= "<tr class='diff-title'><th colspan='4'>$args[title]</th></tr>\n";
if ( $args['title_left'] || $args['title_right'] ) {
$r .= "<tr class='diff-sub-title'>\n";
$r .= "\t<td></td><th>$args[title_left]</th>\n";
$r .= "\t<td></td><th>$args[title_right]</th>\n";
$r .= "</tr>\n";
}
if ( $args['title'] || $args['title_left'] || $args['title_right'] )
$r .= "</thead>\n";
$r .= "<tbody>\n$diff\n</tbody>\n";
$r .= "</table>";
return array( 'html' => $r, 'lines_added' => $lines_added, 'lines_deleted' => $lines_deleted );
}