App Passwords: Improve accessibility.

- Make form inputs stacked instead of inline.
- Provide a visible label for the app name.
- Add screen reader text to dismiss button.
- Make "Revoke" button label more descriptive.
- Use aria-disabled instead of disabled to avoid focus loss.
- Display password in a readonly input to assist copy and paste.
- Remove large sections of italic text.
- Use `.form-wrap` and `.form-field` to give consistent form styling.
- Improve labeling and placeholder text.

Props alexstine, georgestephanis, afercia, TimothyBlynJacobs.
Fixes #51580.

Built from https://develop.svn.wordpress.org/trunk@49294


git-svn-id: http://core.svn.wordpress.org/trunk@49056 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
TimothyBlynJacobs 2020-10-24 03:34:06 +00:00
parent d195b0dfbb
commit f6d39d1c51
12 changed files with 115 additions and 38 deletions

View File

@ -127,7 +127,7 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
<div class="notice notice-error"><p><?php echo $error->get_error_message(); ?></p></div>
<?php endif; ?>
<div class="card js-auth-app-card">
<div class="card auth-app-card">
<h2 class="title"><?php __( 'An application would like to connect to your account.' ); ?></h2>
<?php if ( $app_name ) : ?>
<p>
@ -170,13 +170,13 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
<?php if ( $new_password ) : ?>
<div class="notice notice-success notice-alt below-h2">
<p class="password-display">
<p class="application-password-display">
<?php
printf(
/* translators: 1: Application name, 2: Generated password. */
__( 'Your new password for %1$s is %2$s.' ),
'<strong>' . esc_html( $app_name ) . '</strong>',
'<kbd>' . esc_html( WP_Application_Passwords::chunk_password( $new_password ) ) . '</kbd>'
sprintf( '<input type="text" class="code" readonly="readonly" value="%s" />', esc_attr( WP_Application_Passwords::chunk_password( $new_password ) ) )
);
?>
</p>
@ -195,15 +195,17 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
do_action( 'wp_authorize_application_password_form', $request, $user );
?>
<?php else : ?>
<form action="<?php echo esc_url( admin_url( 'authorize-application.php' ) ); ?>" method="post">
<form action="<?php echo esc_url( admin_url( 'authorize-application.php' ) ); ?>" method="post" class="form-wrap">
<?php wp_nonce_field( 'authorize_application_password' ); ?>
<input type="hidden" name="action" value="authorize_application_password" />
<input type="hidden" name="app_id" value="<?php echo esc_attr( $app_id ); ?>" />
<input type="hidden" name="success_url" value="<?php echo esc_url( $success_url ); ?>" />
<input type="hidden" name="reject_url" value="<?php echo esc_url( $reject_url ); ?>" />
<label for="app_name"><?php esc_html_e( 'Application Title:' ); ?></label>
<input type="text" id="app_name" name="app_name" value="<?php echo esc_attr( $app_name ); ?>" placeholder="<?php esc_attr_e( 'Name this connection&hellip;' ); ?>" required />
<div class="form-field">
<label for="app_name"><?php _e( 'New Application Password Name' ); ?></label>
<input type="text" id="app_name" name="app_name" value="<?php echo esc_attr( $app_name ); ?>" placeholder="<?php esc_attr_e( 'WordPress App on My Phone' ); ?>" required aria-required="true" />
</div>
<?php
/**
@ -223,8 +225,18 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
do_action( 'wp_authorize_application_password_form', $request, $user );
?>
<p><?php submit_button( __( 'Yes, I approve of this connection.' ), 'primary', 'approve', false ); ?>
<br /><em>
<?php
submit_button(
__( 'Yes, I approve of this connection.' ),
'primary',
'approve',
false,
array(
'aria-describedby' => 'description-approve',
)
);
?>
<p class="description" id="description-approve">
<?php
if ( $success_url ) {
printf(
@ -245,11 +257,20 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
_e( 'You will be given a password to manually enter into the application in question.' );
}
?>
</em>
</p>
<p><?php submit_button( __( 'No, I do not approve of this connection.' ), 'secondary', 'reject', false ); ?>
<br /><em>
<?php
submit_button(
__( 'No, I do not approve of this connection.' ),
'secondary',
'reject',
false,
array(
'aria-describedby' => 'description-reject',
)
);
?>
<p class="description" id="description-reject">
<?php
if ( $reject_url ) {
printf(
@ -261,7 +282,6 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
_e( 'You will be returned to the WordPress Dashboard, and no changes will be made.' );
}
?>
</em>
</p>
</form>
<?php endif; ?>

View File

@ -852,11 +852,28 @@ table.form-table td .updated p {
cursor: pointer;
}
.create-application-password .form-field {
max-width: 25em;
}
.create-application-password label {
font-weight: 600;
}
.create-application-password p.submit {
margin-bottom: 0;
padding-bottom: 0;
}
.new-application-password-notice.notice {
margin-top: 20px;
margin-bottom: 0;
}
.application-password-display input.code {
width: 19em;
}
/*------------------------------------------------------------------------------
19.0 - Tools
------------------------------------------------------------------------------*/

File diff suppressed because one or more lines are too long

View File

@ -851,11 +851,28 @@ table.form-table td .updated p {
cursor: pointer;
}
.create-application-password .form-field {
max-width: 25em;
}
.create-application-password label {
font-weight: 600;
}
.create-application-password p.submit {
margin-bottom: 0;
padding-bottom: 0;
}
.new-application-password-notice.notice {
margin-top: 20px;
margin-bottom: 0;
}
.application-password-display input.code {
width: 19em;
}
/*------------------------------------------------------------------------------
19.0 - Tools
------------------------------------------------------------------------------*/

File diff suppressed because one or more lines are too long

View File

@ -104,9 +104,20 @@ class WP_Application_Passwords_List_Table extends WP_List_Table {
* Handles the revoke column output.
*
* @since 5.6.0
*
* @param array $item The current application password item.
*/
public function column_revoke() {
submit_button( __( 'Revoke' ), 'delete', 'revoke-application-password', false );
public function column_revoke( $item ) {
submit_button(
__( 'Revoke' ),
'delete',
'revoke-application-password-' . $item['uuid'],
false,
array(
/* translators: %s: the application password's given name. */
'title' => sprintf( __( 'Revoke "%s"' ), $item['name'] ),
)
);
}
/**
@ -220,7 +231,12 @@ class WP_Application_Passwords_List_Table extends WP_List_Table {
echo "{{ data.last_ip || '—' }}";
break;
case 'revoke':
echo $this->column_revoke();
printf(
'<input type="submit" class="button delete" value="%1$s" title="%2$s">',
esc_attr( __( 'Revoke' ) ),
/* translators: %s: the application password's given name. */
esc_attr( sprintf( __( 'Revoke "%s"' ), '{{ data.name }}' ) )
);
break;
default:
/**

View File

@ -17,6 +17,11 @@
$newAppPassButton.click( function( e ) {
e.preventDefault();
if ( $newAppPassButton.prop( 'aria-disabled' ) ) {
return;
}
var name = $newAppPassField.val();
if ( 0 === name.length ) {
@ -25,8 +30,7 @@
}
clearErrors();
$newAppPassField.prop( 'disabled', true );
$newAppPassButton.prop( 'disabled', true );
$newAppPassButton.prop( 'aria-disabled', true ).addClass( 'disabled' );
var request = {
name: name
@ -47,8 +51,7 @@
method: 'POST',
data: request
} ).always( function() {
$newAppPassField.prop( 'disabled', false );
$newAppPassButton.prop( 'disabled', false );
$newAppPassButton.removeProp( 'aria-disabled' ).removeClass( 'disabled' );
} ).done( function( response ) {
$newAppPassField.val( '' );
$newAppPassButton.prop( 'disabled', false );

View File

@ -1,2 +1,2 @@
/*! This file is auto-generated */
!function(i){var o=i("#application-passwords-section"),n=o.find(".create-application-password"),p=n.find(".input"),s=n.find(".button"),t=o.find(".application-passwords-list-table-wrapper"),r=o.find("tbody"),d=r.find(".no-items"),e=i("#revoke-all-application-passwords"),l=wp.template("new-application-password"),c=wp.template("application-password-row"),w=i("#user_id").val();function u(e,a,o){var p=o;e.responseJSON&&e.responseJSON.message&&(p=e.responseJSON.message),function(e){var a=i("<div></div>").attr("role","alert").addClass("notice notice-error").append(i("<p></p>").text(e));n.after(a)}(p)}function f(){i(".notice",o).remove()}s.click(function(e){e.preventDefault();var a=p.val();if(0!==a.length){f(),p.prop("disabled",!0),s.prop("disabled",!0);var o={name:a};o=wp.hooks.applyFilters("wp_application_passwords_new_password_request",o,w),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"POST",data:o}).always(function(){p.prop("disabled",!1),s.prop("disabled",!1)}).done(function(e){p.val(""),s.prop("disabled",!1),n.after(l({name:a,password:e.password})),i(".new-application-password-notice").focus(),r.prepend(c(e)),t.show(),d.remove(),wp.hooks.doAction("wp_application_passwords_created_password",e,o)}).fail(u)}else p.focus()}),r.on("click",".delete",function(e){if(e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke this password? This action cannot be undone."))){var a=i(this),o=a.closest("tr"),p=o.data("uuid");f(),a.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords/"+p,method:"DELETE"}).always(function(){a.prop("disabled",!1)}).done(function(e){e.deleted&&(0===o.siblings().length&&t.hide(),o.remove(),wp.a11y.speak(wp.i18n.__("Application password revoked.")))}).fail(u)}}),e.on("click",function(e){if(e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke all passwords? This action cannot be undone."))){var a=i(this);f(),a.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"DELETE"}).always(function(){a.prop("disabled",!1)}).done(function(e){e.deleted&&(r.children().remove(),o.children(".new-application-password").remove(),t.hide(),wp.a11y.speak(wp.i18n.__("All application passwords revoked.")))}).fail(u)}}),i(document).on("click",".new-application-password-notice .notice-dismiss",function(e){e.preventDefault();var a=i(this).parent();a.fadeTo(100,0,function(){a.slideUp(100,function(){a.remove()})})}),0===r.children("tr").not(d).length&&t.hide()}(jQuery);
!function(s){var o=s("#application-passwords-section"),n=o.find(".create-application-password"),i=n.find(".input"),p=n.find(".button"),t=o.find(".application-passwords-list-table-wrapper"),r=o.find("tbody"),d=r.find(".no-items"),e=s("#revoke-all-application-passwords"),l=wp.template("new-application-password"),c=wp.template("application-password-row"),w=s("#user_id").val();function u(e,a,o){var i=o;e.responseJSON&&e.responseJSON.message&&(i=e.responseJSON.message),function(e){var a=s("<div></div>").attr("role","alert").addClass("notice notice-error").append(s("<p></p>").text(e));n.after(a)}(i)}function f(){s(".notice",o).remove()}p.click(function(e){if(e.preventDefault(),!p.prop("aria-disabled")){var a=i.val();if(0!==a.length){f(),p.prop("aria-disabled",!0).addClass("disabled");var o={name:a};o=wp.hooks.applyFilters("wp_application_passwords_new_password_request",o,w),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"POST",data:o}).always(function(){p.removeProp("aria-disabled").removeClass("disabled")}).done(function(e){i.val(""),p.prop("disabled",!1),n.after(l({name:a,password:e.password})),s(".new-application-password-notice").focus(),r.prepend(c(e)),t.show(),d.remove(),wp.hooks.doAction("wp_application_passwords_created_password",e,o)}).fail(u)}else i.focus()}}),r.on("click",".delete",function(e){if(e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke this password? This action cannot be undone."))){var a=s(this),o=a.closest("tr"),i=o.data("uuid");f(),a.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords/"+i,method:"DELETE"}).always(function(){a.prop("disabled",!1)}).done(function(e){e.deleted&&(0===o.siblings().length&&t.hide(),o.remove(),wp.a11y.speak(wp.i18n.__("Application password revoked.")))}).fail(u)}}),e.on("click",function(e){if(e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke all passwords? This action cannot be undone."))){var a=s(this);f(),a.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"DELETE"}).always(function(){a.prop("disabled",!1)}).done(function(e){e.deleted&&(r.children().remove(),o.children(".new-application-password").remove(),t.hide(),wp.a11y.speak(wp.i18n.__("All application passwords revoked.")))}).fail(u)}}),s(document).on("click",".new-application-password-notice .notice-dismiss",function(e){e.preventDefault();var a=s(this).parent();a.fadeTo(100,0,function(){a.slideUp(100,function(){a.remove()})})}),0===r.children("tr").not(d).length&&t.hide()}(jQuery);

View File

@ -21,13 +21,16 @@
e.preventDefault();
if ( $approveBtn.prop( 'aria-disabled' ) ) {
return;
}
if ( 0 === name.length ) {
$appNameField.focus();
return;
}
$appNameField.prop( 'disabled', true );
$approveBtn.prop( 'disabled', true );
$approveBtn.prop( 'aria-disabled', true ).addClass( 'disabled' );
var request = {
name: name
@ -82,17 +85,17 @@
message = wp.i18n.sprintf(
wp.i18n.__( 'Your new password for %1$s is: %2$s.' ),
'<strong></strong>',
'<kbd></kbd>'
'<input type="text" class="code" readonly="readonly" value="" />'
);
$notice = $( '<div></div>' )
.attr( 'role', 'alert' )
.attr( 'tabindex', 0 )
.addClass( 'notice notice-success notice-alt' )
.append( $( '<p></p>' ).html( message ) );
.append( $( '<p></p>' ).addClass( 'application-password-display' ).html( message ) );
// We're using .text() to write the variables to avoid any chance of XSS.
$( 'strong', $notice ).text( name );
$( 'kbd', $notice ).text( response.password );
$( 'input', $notice ).val( response.password );
$form.replaceWith( $notice );
$notice.focus();
@ -116,8 +119,7 @@
$( 'h1' ).after( $notice );
$appNameField.prop( 'disabled', false );
$approveBtn.prop( 'disabled', false );
$approveBtn.removeProp( 'aria-disabled', false ).removeClass( 'disabled' );
/**
* Fires when an Authorize Application Password request encountered an error when trying to approve the request.

View File

@ -1,2 +1,2 @@
/*! This file is auto-generated */
!function(i,c){var n=i("#app_name"),d=i("#approve"),e=i("#reject"),l=n.closest("form"),o={userLogin:c.user_login,successUrl:c.success,rejectUrl:c.reject};d.click(function(e){var r=n.val(),s=i('input[name="app_id"]',l).val();if(e.preventDefault(),0!==r.length){n.prop("disabled",!0),d.prop("disabled",!0);var p={name:r};0<s.length&&(p.app_id=s),p=wp.hooks.applyFilters("wp_application_passwords_approve_app_request",p,o),wp.apiRequest({path:"/wp/v2/users/me/application-passwords",method:"POST",data:p}).done(function(e,s,p){wp.hooks.doAction("wp_application_passwords_approve_app_request_success",e,s,p);var o,a,t,n=c.success;n?(o=n+(-1===n.indexOf("?")?"?":"&")+"site_url="+encodeURIComponent(c.site_url)+"&user_login="+encodeURIComponent(c.user_login)+"&password="+encodeURIComponent(e.password),window.location=o):(a=wp.i18n.sprintf(wp.i18n.__("Your new password for %1$s is: %2$s."),"<strong></strong>","<kbd></kbd>"),t=i("<div></div>").attr("role","alert").attr("tabindex",0).addClass("notice notice-success notice-alt").append(i("<p></p>").html(a)),i("strong",t).text(r),i("kbd",t).text(e.password),l.replaceWith(t),t.focus())}).fail(function(e,s,p){var o=p,a=null;e.responseJSON&&(a=e.responseJSON).message&&(o=a.message);var t=i("<div></div>").attr("role","alert").addClass("notice notice-error").append(i("<p></p>").text(o));i("h1").after(t),n.prop("disabled",!1),d.prop("disabled",!1),wp.hooks.doAction("wp_application_passwords_approve_app_request_success",a,s,e)})}else n.focus()}),e.click(function(e){e.preventDefault(),wp.hooks.doAction("wp_application_passwords_reject_app",o),window.location=c.reject}),l.on("submit",function(e){e.preventDefault()})}(jQuery,authApp);
!function(i,l){var p=i("#app_name"),r=i("#approve"),e=i("#reject"),d=p.closest("form"),o={userLogin:l.user_login,successUrl:l.success,rejectUrl:l.reject};r.click(function(e){var n=p.val(),s=i('input[name="app_id"]',d).val();if(e.preventDefault(),!r.prop("aria-disabled"))if(0!==n.length){r.prop("aria-disabled",!0).addClass("disabled");var a={name:n};0<s.length&&(a.app_id=s),a=wp.hooks.applyFilters("wp_application_passwords_approve_app_request",a,o),wp.apiRequest({path:"/wp/v2/users/me/application-passwords",method:"POST",data:a}).done(function(e,s,a){wp.hooks.doAction("wp_application_passwords_approve_app_request_success",e,s,a);var p,o,t,r=l.success;r?(p=r+(-1===r.indexOf("?")?"?":"&")+"site_url="+encodeURIComponent(l.site_url)+"&user_login="+encodeURIComponent(l.user_login)+"&password="+encodeURIComponent(e.password),window.location=p):(o=wp.i18n.sprintf(wp.i18n.__("Your new password for %1$s is: %2$s."),"<strong></strong>",'<input type="text" class="code" readonly="readonly" value="" />'),t=i("<div></div>").attr("role","alert").attr("tabindex",0).addClass("notice notice-success notice-alt").append(i("<p></p>").addClass("application-password-display").html(o)),i("strong",t).text(n),i("input",t).val(e.password),d.replaceWith(t),t.focus())}).fail(function(e,s,a){var p=a,o=null;e.responseJSON&&(o=e.responseJSON).message&&(p=o.message);var t=i("<div></div>").attr("role","alert").addClass("notice notice-error").append(i("<p></p>").text(p));i("h1").after(t),r.removeProp("aria-disabled",!1).removeClass("disabled"),wp.hooks.doAction("wp_application_passwords_approve_app_request_success",o,s,e)})}else p.focus()}),e.click(function(e){e.preventDefault(),wp.hooks.doAction("wp_application_passwords_reject_app",o),window.location=l.reject}),d.on("submit",function(e){e.preventDefault()})}(jQuery,authApp);

View File

@ -739,9 +739,11 @@ endif;
}
}
?>
<div class="create-application-password">
<label for="new_application_password_name" class="screen-reader-text"><?php _e( 'New Application Password Name' ); ?></label>
<input type="text" size="30" id="new_application_password_name" name="new_application_password_name" placeholder="<?php esc_attr_e( 'New Application Password Name' ); ?>" class="input" />
<div class="create-application-password form-wrap">
<div class="form-field">
<label for="new_application_password_name"><?php _e( 'New Application Password Name' ); ?></label>
<input type="text" size="30" id="new_application_password_name" name="new_application_password_name" placeholder="<?php esc_attr_e( 'WordPress App on My Phone' ); ?>" class="input" />
</div>
<?php
/**
@ -754,7 +756,7 @@ endif;
do_action( 'wp_create_application_password_form', $profileuser );
?>
<?php submit_button( __( 'Add New' ), 'secondary', 'do_new_application_password', false ); ?>
<?php submit_button( __( 'Add New' ), 'secondary', 'do_new_application_password' ); ?>
</div>
<div class="application-passwords-list-table-wrapper">
@ -856,19 +858,19 @@ endif;
<?php if ( isset( $application_passwords_list_table ) ) : ?>
<script type="text/html" id="tmpl-new-application-password">
<div class="notice notice-success is-dismissible new-application-password-notice" role="alert" tabindex="0">
<p>
<p class="application-password-display">
<?php
printf(
/* translators: 1: Application name, 2: Generated password. */
esc_html__( 'Your new password for %1$s is: %2$s' ),
'<strong>{{ data.name }}</strong>',
'<kbd>{{ data.password }}</kbd>'
'<input type="text" class="code" readonly="readonly" value="{{ data.password }}" />'
);
?>
</p>
<p><?php esc_attr_e( 'Be sure to save this in a safe location. You will not be able to retrieve it.' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php __( 'Dismiss this notice.' ); ?></span>
<span class="screen-reader-text"><?php _e( 'Dismiss this notice.' ); ?></span>
</button>
</div>
</script>

View File

@ -13,7 +13,7 @@
*
* @global string $wp_version
*/
$wp_version = '5.6-beta1-49293';
$wp_version = '5.6-beta1-49294';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.