Skip to content

Commit 585ec6c

Browse files
committed
Administration: Prevent the Dashboard Quick Draft widget from creating a draft with empty title and empty content.
Fixes a regression from WordPress 5.4 by making sure the Paragraph block markup is only added when there is some actual content. Otherwise, WordPress will consider the post content 'not empty' and would allow creating a draft with no actual content and no title. - Also makes sure the Paragraph block markup is only added when the block editor is actually in use. - Adds failure and success admin notices. - Adds a `role="alert"` attribute to the admin notices so that they are automatically announced by screen readers. - Adds a `$notice_type` parameter to `wp_dashboard_quick_press()`. - Cleans up the related JS from dead code that doesn't do anything. - Prevents from highlighting the most recent draft when no new draft has been actually created. - Cleans up the related CSS. - Adds E2E tests. Props sumitsingh, huzaifaalmesbah, opurockey, westonruter, SergeyBiryukov, torontodigits, audrasjb, aion11, sanayasir, joedolson, afercia. Fixes #64952. git-svn-id: https://develop.svn.wordpress.org/trunk@62670 602fd350-edb4-49c9-b593-d223f7449a82
1 parent e71d6ec commit 585ec6c

5 files changed

Lines changed: 165 additions & 57 deletions

File tree

src/js/_enqueues/wp/dashboard.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
/* global pagenow, ajaxurl, postboxes, wpActiveEditor:true, ajaxWidgets */
6-
/* global ajaxPopulateWidgets, quickPressLoad, */
6+
/* global ajaxPopulateWidgets, quickPressLoad */
77
window.wp = window.wp || {};
88
window.communityEventsData = window.communityEventsData || {};
99

@@ -137,52 +137,54 @@ jQuery( function($) {
137137
* @return {void}
138138
*/
139139
window.quickPressLoad = function() {
140-
var act = $('#quickpost-action'), t;
140+
var act = $( '#quickpost-action' ), t;
141141

142-
// Enable the submit buttons.
143-
$( '#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]' ).prop( 'disabled' , false );
142+
// Enable the submit button.
143+
$( '#quick-press .submit input[type="submit"]' ).prop( 'disabled', false );
144144

145-
t = $('#quick-press').on( 'submit', function( e ) {
145+
t = $( '#quick-press' ).on( 'submit', function( e ) {
146146
e.preventDefault();
147147

148-
// Show a spinner.
149-
$('#dashboard_quick_press #publishing-action .spinner').show();
150-
151148
// Disable the submit button to prevent duplicate submissions.
152-
$('#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]').prop('disabled', true);
149+
$( '#quick-press .submit input[type="submit"]' ).prop( 'disabled', true );
153150

154151
// Post the entered data to save it.
155152
$.post( t.attr( 'action' ), t.serializeArray(), function( data ) {
156153
// Replace the form, and prepend the published post.
157-
$('#dashboard_quick_press .inside').html( data );
158-
$('#quick-press').removeClass('initial-form');
154+
$( '#dashboard_quick_press .inside' ).html( data );
159155
quickPressLoad();
160156
highlightLatestPost();
161157

162158
// Focus the title to allow for quickly drafting another post.
163-
$('#title').trigger( 'focus' );
164-
});
159+
$( '#title' ).trigger( 'focus');
160+
} );
165161

166162
/**
167163
* Highlights the latest post for one second.
168164
*
169165
* @return {void}
170166
*/
171167
function highlightLatestPost () {
172-
var latestPost = $('.drafts ul li').first();
173-
latestPost.css('background', '#fffbe5');
174-
setTimeout(function () {
175-
latestPost.css('background', 'none');
176-
}, 1000);
168+
var latestPost = $( '.drafts ul li' ) .first(),
169+
errorNotice = $( '#quick-press .notice-error' );
170+
171+
if ( errorNotice.length ) {
172+
return;
173+
}
174+
175+
latestPost.css( 'background', '#fffbe5' );
176+
setTimeout( function () {
177+
latestPost.css( 'background', 'none' );
178+
}, 1000 );
177179
}
178180
} );
179181

180182
// Change the QuickPost action to the publish value.
181-
$('#publish').on( 'click', function() { act.val( 'post-quickpress-publish' ); } );
183+
$( '#publish' ).on( 'click', function() { act.val( 'post-quickpress-publish' ); } );
182184

183-
$('#quick-press').on( 'click focusin', function() {
185+
$( '#quick-press' ).on( 'click focusin', function() {
184186
wpActiveEditor = 'content';
185-
});
187+
} );
186188

187189
autoResizeTextarea();
188190
};

src/wp-admin/css/dashboard.css

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -780,12 +780,6 @@ body #dashboard-widgets .postbox form .submit {
780780
padding: 0;
781781
}
782782

783-
#dashboard_quick_press div.updated {
784-
margin-bottom: 10px;
785-
border: 1px solid #f0f0f1;
786-
border-width: 1px 1px 1px 0;
787-
}
788-
789783
#dashboard_quick_press form {
790784
margin: 12px;
791785
}
@@ -803,7 +797,6 @@ body #dashboard-widgets .postbox form .submit {
803797

804798
#dashboard_quick_press input,
805799
#dashboard_quick_press textarea {
806-
box-sizing: border-box;
807800
margin: 0;
808801
}
809802

@@ -850,11 +843,12 @@ body #dashboard-widgets .postbox form .submit {
850843
#dashboard_quick_press .drafts li {
851844
margin-bottom: 1em;
852845
}
846+
853847
#dashboard_quick_press .drafts li time {
854848
color: #646970;
855849
}
856850

857-
#dashboard_quick_press .drafts p {
851+
#dashboard_quick_press .drafts .draft-content {
858852
margin: 0;
859853
word-wrap: break-word;
860854
}

src/wp-admin/includes/dashboard.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -541,12 +541,14 @@ function wp_network_dashboard_right_now() {
541541
* Displays the Quick Draft widget.
542542
*
543543
* @since 3.8.0
544+
* @since 7.1.0 Added $notice_type parameter.
544545
*
545546
* @global int $post_ID
546547
*
547-
* @param string|false $error_msg Optional. Error message. Default false.
548+
* @param string|false $message Optional. Error or success message. Default false.
549+
* @param string $notice_type Optional. Admin notice type. Default 'error'.
548550
*/
549-
function wp_dashboard_quick_press( $error_msg = false ) {
551+
function wp_dashboard_quick_press( $message = false, $notice_type = 'error' ) {
550552
global $post_ID;
551553

552554
if ( ! current_user_can( 'edit_posts' ) ) {
@@ -578,14 +580,17 @@ function wp_dashboard_quick_press( $error_msg = false ) {
578580
$post_ID = (int) $post->ID;
579581
?>
580582

581-
<form name="post" action="<?php echo esc_url( admin_url( 'post.php' ) ); ?>" method="post" id="quick-press" class="initial-form hide-if-no-js">
583+
<form name="post" action="<?php echo esc_url( admin_url( 'post.php' ) ); ?>" method="post" id="quick-press" class="hide-if-no-js">
582584

583585
<?php
584-
if ( $error_msg ) {
586+
if ( $message ) {
585587
wp_admin_notice(
586-
$error_msg,
588+
$message,
587589
array(
588-
'additional_classes' => array( 'error' ),
590+
'type' => $notice_type,
591+
'attributes' => array(
592+
'role' => 'alert',
593+
),
589594
)
590595
);
591596
}
@@ -688,7 +693,7 @@ function wp_dashboard_recent_drafts( $drafts = false ) {
688693
$the_content = wp_trim_words( $draft->post_content, $draft_length );
689694

690695
if ( $the_content ) {
691-
echo '<p>' . $the_content . '</p>';
696+
echo '<p class="draft-content">' . $the_content . '</p>';
692697
}
693698
echo "</li>\n";
694699
}

src/wp-admin/post.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,28 @@
9696
$_POST['comment_status'] = get_default_comment_status( $post->post_type );
9797
$_POST['ping_status'] = get_default_comment_status( $post->post_type, 'pingback' );
9898

99-
// Wrap Quick Draft content in the Paragraph block.
100-
if ( ! str_contains( $_POST['content'], '<!-- wp:paragraph -->' ) ) {
99+
$quickdraft_post_title = trim( $_POST['post_title'] );
100+
$quickdraft_post_content = trim( $_POST['content'] );
101+
102+
if ( empty( $quickdraft_post_title ) && empty( $quickdraft_post_content ) ) {
103+
return wp_dashboard_quick_press( __( 'Cannot create a draft post with empty title and content.' ) );
104+
}
105+
106+
// Wrap Quick Draft content in a Paragraph block.
107+
if (
108+
use_block_editor_for_post_type( $post->post_type ) &&
109+
! empty( $quickdraft_post_content ) &&
110+
! str_contains( $quickdraft_post_content, '<!-- wp:paragraph -->' )
111+
) {
112+
// Note that `edit_post()` reads from the $_POST superglobal by reference.
101113
$_POST['content'] = sprintf(
102114
'<!-- wp:paragraph -->%s<!-- /wp:paragraph -->',
103-
str_replace( array( "\r\n", "\r", "\n" ), '<br />', $_POST['content'] )
115+
str_replace( array( "\r\n", "\r", "\n" ), '<br />', $quickdraft_post_content )
104116
);
105117
}
106118

107119
edit_post();
108-
wp_dashboard_quick_press();
120+
wp_dashboard_quick_press( __( 'Draft created successfully.' ), 'success' );
109121
exit;
110122

111123
case 'postajaxpost':

tests/e2e/specs/dashboard.test.js

Lines changed: 112 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,63 +8,158 @@ test.describe( 'Quick Draft', () => {
88
await requestUtils.deleteAllPosts();
99
} );
1010

11-
test( 'Allows draft to be created with Title and Content', async ( {
11+
test( 'should allow Quick Draft to be created with Title and Content', async ( {
1212
admin,
1313
page
1414
} ) => {
1515
await admin.visitAdminPage( '/' );
1616

17-
// Wait for Quick Draft title field to appear.
17+
// Wait for the Quick Draft title field to appear.
1818
const draftTitleField = page.locator(
1919
'#quick-press'
2020
).getByRole( 'textbox', { name: 'Title' } );
2121

2222
await expect( draftTitleField ).toBeVisible();
2323

2424
// Focus and fill in a title.
25-
await draftTitleField.fill( 'Test Draft Title' );
25+
await draftTitleField.fill( 'Quick Draft test title' );
2626

27-
// Navigate to content field and type in some content
28-
await page.keyboard.press( 'Tab' );
29-
await page.keyboard.type( 'Test Draft Content' );
27+
// Wait for the Quick Draft content textarea to appear.
28+
const quickDraftContentTextarea = page.locator(
29+
'#quick-press'
30+
).getByRole( 'textbox', { name: 'Content' } );
31+
32+
await expect( quickDraftContentTextarea ).toBeVisible();
33+
34+
// Focus and fill in some content.
35+
await quickDraftContentTextarea.fill( 'Quick Draft test content' );
36+
37+
// Wait for the Save Draft button to appear and click it.
38+
const saveDraftButton = page.locator(
39+
'#quick-press'
40+
).getByRole( 'button', { name: 'Save Draft' } );
3041

31-
// Navigate to Save Draft button and press it.
32-
await page.keyboard.press( 'Tab' );
33-
await page.keyboard.press( 'Enter' );
42+
await expect( saveDraftButton ).toBeVisible();
43+
await saveDraftButton.click();
3444

35-
// Check that new draft appears in Your Recent Drafts section
45+
// Check that the new draft title appears in the 'Your Recent Drafts' section.
3646
await expect(
3747
page.locator( '.drafts .draft-title' ).first().getByRole( 'link' )
38-
).toHaveText( 'Test Draft Title' );
48+
).toHaveText( 'Quick Draft test title' );
49+
50+
// Check that the new draft content appears in the 'Your Recent Drafts' section.
51+
await expect(
52+
page.locator( '.drafts .draft-content' ).first()
53+
).toHaveText( 'Quick Draft test content' );
3954

40-
// Check that new draft appears in Posts page
55+
// Check that the new draft appears in the Posts page.
4156
await admin.visitAdminPage( '/edit.php' );
4257

4358
await expect(
4459
page.locator( '.type-post.status-draft .title' ).first()
45-
).toContainText( 'Test Draft Title' );
60+
).toContainText( 'Quick Draft test title' );
4661
} );
4762

48-
test( 'Allows draft to be created without Title or Content', async ( {
63+
test( 'should prevent Quick Draft from being created without Title and Content', async ( {
4964
admin,
5065
page
5166
} ) => {
5267
await admin.visitAdminPage( '/' );
5368

54-
// Wait for Save Draft button to appear and click it
69+
// Wait for the Save Draft button to appear and click it.
5570
const saveDraftButton = page.locator(
5671
'#quick-press'
5772
).getByRole( 'button', { name: 'Save Draft' } );
5873

5974
await expect( saveDraftButton ).toBeVisible();
6075
await saveDraftButton.click();
6176

62-
// Check that new draft appears in Your Recent Drafts section
77+
// Check that an admin notice with ARIA role 'alert' appears.
78+
await expect(
79+
page.locator( '#quick-press' ).getByRole( 'alert' )
80+
).toHaveText( 'Cannot create a draft post with empty title and content.' );
81+
82+
// Check that no new draft appears in the Posts page.
83+
await admin.visitAdminPage( '/edit.php' );
84+
85+
await expect(
86+
page.locator( '#the-list .no-items .colspanchange' )
87+
).toContainText( 'No posts found.' );
88+
} );
89+
90+
test( 'should allow Quick Draft to be created with only the Title', async ( {
91+
admin,
92+
page
93+
} ) => {
94+
await admin.visitAdminPage( '/' );
95+
96+
// Wait for the Quick Draft title field to appear.
97+
const quickDraftTitleField = page.locator(
98+
'#quick-press'
99+
).getByRole( 'textbox', { name: 'Title' } );
100+
101+
await expect( quickDraftTitleField ).toBeVisible();
102+
103+
// Focus and fill in a title.
104+
await quickDraftTitleField.fill( 'Quick Draft test title' );
105+
106+
// Wait for the Save Draft button to appear and click it.
107+
const saveDraftButton = page.locator(
108+
'#quick-press'
109+
).getByRole( 'button', { name: 'Save Draft' } );
110+
111+
await expect( saveDraftButton ).toBeVisible();
112+
await saveDraftButton.click();
113+
114+
// Check that the new draft title appears in the 'Your Recent Drafts' section.
115+
await expect(
116+
page.locator( '.drafts .draft-title' ).first().getByRole( 'link' )
117+
).toHaveText( 'Quick Draft test title' );
118+
119+
// Check that the new draft appears in the Posts page.
120+
await admin.visitAdminPage( '/edit.php' );
121+
122+
await expect(
123+
page.locator( '.type-post.status-draft .title' ).first()
124+
).toContainText( 'Quick Draft test title' );
125+
} );
126+
127+
test( 'should allow Quick Draft to be created with only the Content', async ( {
128+
admin,
129+
page
130+
} ) => {
131+
await admin.visitAdminPage( '/' );
132+
133+
// Wait for the Quick Draft content textarea to appear.
134+
const quickDraftContentTextarea = page.locator(
135+
'#quick-press'
136+
).getByRole( 'textbox', { name: 'Content' } );
137+
138+
await expect( quickDraftContentTextarea ).toBeVisible();
139+
140+
// Focus and fill in some content.
141+
await quickDraftContentTextarea.fill( 'Quick Draft test content' );
142+
143+
// Wait for the Save Draft button to appear and click it.
144+
const saveDraftButton = page.locator(
145+
'#quick-press'
146+
).getByRole( 'button', { name: 'Save Draft' } );
147+
148+
await expect( saveDraftButton ).toBeVisible();
149+
await saveDraftButton.click();
150+
151+
// Check that the new draft title appears in the 'Your Recent Drafts' section.
152+
// This test relies on Twenty Twenty-One being the active theme.
153+
// Twenty Twenty-One alters the default post title from "(no title)" to "Untitled".
63154
await expect(
64155
page.locator( '.drafts .draft-title' ).first().getByRole( 'link' )
65156
).toHaveText( 'Untitled' );
66157

67-
// Check that new draft appears in Posts page
158+
await expect(
159+
page.locator( '.drafts .draft-content' ).first()
160+
).toHaveText( 'Quick Draft test content' );
161+
162+
// Check that the new draft appears in the Posts page.
68163
await admin.visitAdminPage( '/edit.php' );
69164

70165
await expect(

0 commit comments

Comments
 (0)