App Passwords: Further accessibility improvements.

- Add a label to the readonly password input.
- Handle focus loss after revoking app passwords.
- Handle focus loss after dismissing notices.
- Mark app name as `aria-required`.
- Use `aria-label` for detailed revoke button text instead of `title`.
- Use `-1` for `tabindex` instead of `0`.

Props alexstine, afercia, sabernhardt, audrasjb, joedolson, TimothyBlynJacobs.
Fixes #51580.

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


git-svn-id: http://core.svn.wordpress.org/trunk@49287 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
TimothyBlynJacobs 2020-11-09 18:05:08 +00:00
parent 4040866754
commit 74969e7fbe
13 changed files with 78 additions and 51 deletions

View File

@ -171,15 +171,18 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
<?php if ( $new_password ) : ?>
<div class="notice notice-success notice-alt below-h2">
<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>',
sprintf( '<input type="text" class="code" readonly="readonly" value="%s" />', esc_attr( WP_Application_Passwords::chunk_password( $new_password ) ) )
);
?>
<label for="new-application-password-value">
<?php
printf(
/* translators: %s: Application name */
esc_html__( 'Your new password for %s is:' ),
'<strong>' . esc_html( $app_name ) . '</strong>'
);
?>
</label>
<input id="new-application-password-value" type="text" class="code" readonly="readonly" value="<?php esc_attr( WP_Application_Passwords::chunk_password( $new_password ) ); ?>" />
</p>
<p><?php _e( 'Be sure to save this in a safe location. You will not be able to retrieve it.' ); ?></p>
</div>
<?php
@ -204,7 +207,7 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
<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" />
<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 />
</div>
<?php

View File

@ -865,7 +865,7 @@ table.form-table td .updated p {
padding-bottom: 0;
}
.new-application-password-notice.notice {
#application-passwords-section .notice {
margin-top: 20px;
margin-bottom: 0;
}
@ -874,6 +874,10 @@ table.form-table td .updated p {
width: 19em;
}
.auth-app-card.card {
max-width: 768px;
}
/*------------------------------------------------------------------------------
19.0 - Tools
------------------------------------------------------------------------------*/

File diff suppressed because one or more lines are too long

View File

@ -864,7 +864,7 @@ table.form-table td .updated p {
padding-bottom: 0;
}
.new-application-password-notice.notice {
#application-passwords-section .notice {
margin-top: 20px;
margin-bottom: 0;
}
@ -873,6 +873,10 @@ table.form-table td .updated p {
width: 19em;
}
.auth-app-card.card {
max-width: 768px;
}
/*------------------------------------------------------------------------------
19.0 - Tools
------------------------------------------------------------------------------*/

File diff suppressed because one or more lines are too long

View File

@ -117,7 +117,7 @@ class WP_Application_Passwords_List_Table extends WP_List_Table {
false,
array(
/* translators: %s: the application password's given name. */
'title' => sprintf( __( 'Revoke "%s"' ), $item['name'] ),
'aria-label' => sprintf( __( 'Revoke "%s"' ), $item['name'] ),
)
);
}
@ -234,7 +234,7 @@ class WP_Application_Passwords_List_Table extends WP_List_Table {
break;
case 'revoke':
printf(
'<input type="submit" class="button delete" value="%1$s" title="%2$s">',
'<input type="submit" class="button delete" value="%1$s" aria-label="%2$s">',
esc_attr( __( 'Revoke' ) ),
/* translators: %s: the application password's given name. */
esc_attr( sprintf( __( 'Revoke "%s"' ), '{{ data.name }}' ) )

View File

@ -29,7 +29,7 @@
return;
}
clearErrors();
clearNotices();
$newAppPassButton.prop( 'aria-disabled', true ).addClass( 'disabled' );
var request = {
@ -90,7 +90,7 @@
$tr = $submitButton.closest( 'tr' ),
uuid = $tr.data( 'uuid' );
clearErrors();
clearNotices();
$submitButton.prop( 'disabled', true );
wp.apiRequest( {
@ -105,7 +105,7 @@
}
$tr.remove();
wp.a11y.speak( wp.i18n.__( 'Application password revoked.' ) );
addNotice( wp.i18n.__( 'Application password revoked.' ), 'success' ).focus();
}
} ).fail( handleErrorResponse );
} );
@ -119,7 +119,7 @@
var $submitButton = $( this );
clearErrors();
clearNotices();
$submitButton.prop( 'disabled', true );
wp.apiRequest( {
@ -133,17 +133,19 @@
$appPassSection.children( '.new-application-password' ).remove();
$appPassTwrapper.hide();
wp.a11y.speak( wp.i18n.__( 'All application passwords revoked.' ) );
addNotice( wp.i18n.__( 'All application passwords revoked.' ), 'success' ).focus();
}
} ).fail( handleErrorResponse );
} );
$( document ).on( 'click', '.new-application-password-notice .notice-dismiss', function( e ) {
$appPassSection.on( 'click', '.notice-dismiss', function( e ) {
e.preventDefault();
var $el = $( this ).parent();
$el.removeAttr( 'role' );
$el.fadeTo( 100, 0, function () {
$el.slideUp( 100, function () {
$el.remove();
$newAppPassField.focus();
} );
} );
} );
@ -169,31 +171,42 @@
errorMessage = xhr.responseJSON.message;
}
addError( errorMessage );
addNotice( errorMessage, 'error' );
}
/**
* Displays an error message in the Application Passwords section.
* Displays a message in the Application Passwords section.
*
* @since 5.6.0
*
* @param {string} message The error message to display.
* @param {string} message The message to display.
* @param {string} type The notice type. Either 'success' or 'error'.
* @returns {jQuery} The notice element.
*/
function addError( message ) {
function addNotice( message, type ) {
var $notice = $( '<div></div>' )
.attr( 'role', 'alert' )
.addClass( 'notice notice-error' )
.append( $( '<p></p>' ).text( message ) );
.attr( 'tabindex', '-1' )
.addClass( 'is-dismissible notice notice-' + type )
.append( $( '<p></p>' ).text( message ) )
.append(
$( '<button></button>' )
.attr( 'type', 'button' )
.addClass( 'notice-dismiss' )
.append( $( '<span></span>' ).addClass( 'screen-reader-text' ).text( wp.i18n.__( 'Dismiss this notice.' ) ) )
);
$newAppPassForm.after( $notice );
return $notice;
}
/**
* Clears error messages from the Application Passwords section.
* Clears notice messages from the Application Passwords section.
*
* @since 5.6.0
*/
function clearErrors() {
function clearNotices() {
$( '.notice', $appPassSection ).remove();
}
}( jQuery ) );

View File

@ -1,2 +1,2 @@
/*! This file is auto-generated */
!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);
!function(i){var a=i("#application-passwords-section"),o=a.find(".create-application-password"),n=o.find(".input"),t=o.find(".button"),p=a.find(".application-passwords-list-table-wrapper"),r=a.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,s,a){var o=a;e.responseJSON&&e.responseJSON.message&&(o=e.responseJSON.message),f(o,"error")}function f(e,s){var a=i("<div></div>").attr("role","alert").attr("tabindex","-1").addClass("is-dismissible notice notice-"+s).append(i("<p></p>").text(e)).append(i("<button></button>").attr("type","button").addClass("notice-dismiss").append(i("<span></span>").addClass("screen-reader-text").text(wp.i18n.__("Dismiss this notice."))));return o.after(a),a}function v(){i(".notice",a).remove()}t.click(function(e){if(e.preventDefault(),!t.prop("aria-disabled")){var s=n.val();if(0!==s.length){v(),t.prop("aria-disabled",!0).addClass("disabled");var a={name:s};a=wp.hooks.applyFilters("wp_application_passwords_new_password_request",a,w),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"POST",data:a}).always(function(){t.removeProp("aria-disabled").removeClass("disabled")}).done(function(e){n.val(""),t.prop("disabled",!1),o.after(l({name:s,password:e.password})),i(".new-application-password-notice").focus(),r.prepend(c(e)),p.show(),d.remove(),wp.hooks.doAction("wp_application_passwords_created_password",e,a)}).fail(u)}else n.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 s=i(this),a=s.closest("tr"),o=a.data("uuid");v(),s.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords/"+o,method:"DELETE"}).always(function(){s.prop("disabled",!1)}).done(function(e){e.deleted&&(0===a.siblings().length&&p.hide(),a.remove(),f(wp.i18n.__("Application password revoked."),"success").focus())}).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 s=i(this);v(),s.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"DELETE"}).always(function(){s.prop("disabled",!1)}).done(function(e){e.deleted&&(r.children().remove(),a.children(".new-application-password").remove(),p.hide(),f(wp.i18n.__("All application passwords revoked."),"success").focus())}).fail(u)}}),a.on("click",".notice-dismiss",function(e){e.preventDefault();var s=i(this).parent();s.removeAttr("role"),s.fadeTo(100,0,function(){s.slideUp(100,function(){s.remove(),n.focus()})})}),0===r.children("tr").not(d).length&&p.hide()}(jQuery);

View File

@ -83,15 +83,16 @@
window.location = url;
} else {
message = wp.i18n.sprintf(
wp.i18n.__( 'Your new password for %1$s is: %2$s.' ),
'<strong></strong>',
'<input type="text" class="code" readonly="readonly" value="" />'
);
/* translators: %s: Application name */
'<label for="new-application-password-value">' + wp.i18n.__( 'Your new password for %s is:' ) + '</label>',
'<strong></strong>'
) + ' <input id="new-application-password-value" type="text" class="code" readonly="readonly" value="" />';
$notice = $( '<div></div>' )
.attr( 'role', 'alert' )
.attr( 'tabindex', 0 )
.attr( 'tabindex', -1 )
.addClass( 'notice notice-success notice-alt' )
.append( $( '<p></p>' ).addClass( 'application-password-display' ).html( message ) );
.append( $( '<p></p>' ).addClass( 'application-password-display' ).html( message ) )
.append( '<p>' + wp.i18n.__( 'Be sure to save this in a safe location. You will not be able to retrieve it.' ) + '</p>' );
// We're using .text() to write the variables to avoid any chance of XSS.
$( 'strong', $notice ).text( name );

View File

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

@ -742,7 +742,7 @@ endif;
<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" />
<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" aria-required="true" aria-describedby="<?php esc_attr_e( 'Required to create an Application Password, but not to update the user.' ); ?>" />
</div>
<?php
@ -756,7 +756,7 @@ endif;
do_action( 'wp_create_application_password_form', $profileuser );
?>
<?php submit_button( __( 'Add New' ), 'secondary', 'do_new_application_password' ); ?>
<?php submit_button( __( 'Add New Application Password' ), 'secondary', 'do_new_application_password' ); ?>
</div>
<div class="application-passwords-list-table-wrapper">
@ -857,18 +857,20 @@ 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">
<div class="notice notice-success is-dismissible new-application-password-notice" role="alert" tabindex="-1">
<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>',
'<input type="text" class="code" readonly="readonly" value="{{ data.password }}" />'
);
?>
<label for="new-application-password-value">
<?php
printf(
/* translators: %s: Application name */
__( 'Your new password for %s is:' ),
'<strong>{{ data.name }}</strong>'
);
?>
</label>
<input id="new-application-password-value" 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>
<p><?php _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 _e( 'Dismiss this notice.' ); ?></span>
</button>

View File

@ -1067,7 +1067,7 @@ function wp_default_scripts( $scripts ) {
);
$scripts->set_translations( 'password-strength-meter' );
$scripts->add( 'application-passwords', "/wp-admin/js/application-passwords$suffix.js", array( 'jquery', 'wp-util', 'wp-api-request', 'wp-date', 'wp-i18n', 'wp-hooks', 'wp-a11y' ), false, 1 );
$scripts->add( 'application-passwords', "/wp-admin/js/application-passwords$suffix.js", array( 'jquery', 'wp-util', 'wp-api-request', 'wp-date', 'wp-i18n', 'wp-hooks' ), false, 1 );
$scripts->set_translations( 'application-passwords' );
$scripts->add( 'auth-app', "/wp-admin/js/auth-app$suffix.js", array( 'jquery', 'wp-api-request', 'wp-i18n', 'wp-hooks' ), false, 1 );

View File

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