Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Forms: update read/unread counts optimistically in the sidebar
Original file line number Diff line number Diff line change
Expand Up @@ -1415,9 +1415,7 @@ public function unread_count() {
$unread = self::get_unread_count();

if ( isset( $submenu['jetpack'] ) && is_array( $submenu['jetpack'] ) && ! empty( $submenu['jetpack'] ) ) {
$inline_style = ( $unread > 0 ) ? '' : 'style="display: none;"';

$forms_unread_count_tag = " <span class='jp-feedback-unread-counter count-{$unread} awaiting-mod' {$inline_style}><span class='feedback-unread-counter'>" . number_format_i18n( $unread ) . '</span></span>';
$forms_unread_count_tag = " <span class='jp-feedback-unread-counter count-{$unread} awaiting-mod'><span class='feedback-unread-counter'>" . number_format_i18n( $unread ) . '</span></span>';
$jetpack_badge_count = $unread;

// Main menu entries
Expand Down
31 changes: 26 additions & 5 deletions projects/packages/forms/src/dashboard/inbox/dataviews/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { store as noticesStore } from '@wordpress/notices';
import { notSpam, spam } from '../../icons';
import { store as dashboardStore } from '../../store';
import InboxResponse from '../response';
import { updateMenuCounter } from '../utils';
import { updateMenuCounter, updateMenuCounterOptimistically } from '../utils';

export const BULK_ACTIONS = {
markAsSpam: 'mark_as_spam',
Expand Down Expand Up @@ -340,8 +340,9 @@ export const markAsReadAction = {
const { editEntityRecord } = registry.dispatch( coreStore );
const { getEntityRecord } = registry.select( coreStore );
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );

const promises = await Promise.allSettled(
items.map( async ( { id } ) => {
items.map( async ( { id, status } ) => {
// Get current entity from store
const currentEntity = getEntityRecord( 'postType', 'feedback', id );

Expand All @@ -350,6 +351,11 @@ export const markAsReadAction = {
editEntityRecord( 'postType', 'feedback', id, {
is_unread: false,
} );

// Immediately update menu counters optimistically to avoid delays, but only for inbox
if ( status === 'publish' ) {
updateMenuCounterOptimistically( -1 );
}
}

// Update on server
Expand All @@ -359,7 +365,7 @@ export const markAsReadAction = {
data: { is_unread: false },
} )
.then( ( { count } ) => {
// Update the unread count in the menu.
// Update menu counter with accurate count from server.
updateMenuCounter( count );
} )
.catch( () => {
Expand All @@ -368,6 +374,11 @@ export const markAsReadAction = {
editEntityRecord( 'postType', 'feedback', id, {
is_unread: true,
} );

// Revert the optimistic change in the sidebar.
if ( status === 'publish' ) {
updateMenuCounterOptimistically( 1 );
}
}
throw new Error( 'Failed to mark as read' );
} );
Expand Down Expand Up @@ -419,7 +430,7 @@ export const markAsUnreadAction = {
const { getEntityRecord } = registry.select( coreStore );
const { createSuccessNotice, createErrorNotice } = registry.dispatch( noticesStore );
const promises = await Promise.allSettled(
items.map( async ( { id } ) => {
items.map( async ( { id, status } ) => {
// Get current entity from store
const currentEntity = getEntityRecord( 'postType', 'feedback', id );

Expand All @@ -428,6 +439,11 @@ export const markAsUnreadAction = {
editEntityRecord( 'postType', 'feedback', id, {
is_unread: true,
} );

// Immediately update menu counters optimistically to avoid delays, but only for inbox
if ( status === 'publish' ) {
updateMenuCounterOptimistically( 1 );
}
}

// Update on server
Expand All @@ -437,7 +453,7 @@ export const markAsUnreadAction = {
data: { is_unread: true },
} )
.then( ( { count } ) => {
// Update the unread count in the menu.
// Update menu counter with accurate count from server.
updateMenuCounter( count );
} )
.catch( () => {
Expand All @@ -446,6 +462,11 @@ export const markAsUnreadAction = {
editEntityRecord( 'postType', 'feedback', id, {
is_unread: false,
} );

// Revert the optimistic change in the sidebar.
if ( status === 'publish' ) {
updateMenuCounterOptimistically( -1 );
}
}
throw new Error( 'Failed to mark as unread' );
} );
Expand Down
14 changes: 13 additions & 1 deletion projects/packages/forms/src/dashboard/inbox/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
markAsReadAction,
markAsUnreadAction,
} from './dataviews/actions';
import { getPath, updateMenuCounter } from './utils';
import { getPath, updateMenuCounter, updateMenuCounterOptimistically } from './utils';

const getDisplayName = response => {
const { author_name, author_email, author_url, ip } = response;
Expand Down Expand Up @@ -550,19 +550,31 @@ const InboxResponse = ( {
is_unread: false,
} );

// Immediately update menu counters optimistically to avoid delays
if ( response.status === 'publish' ) {
updateMenuCounterOptimistically( -1 );
}

// Then update on server
apiFetch( {
path: `/wp/v2/feedback/${ response.id }/read`,
method: 'POST',
data: { is_unread: false },
} )
.then( ( { count } ) => {
// Update menu counter with accurate count from server
updateMenuCounter( count );
} )
.catch( () => {
// Revert the change in the store
editEntityRecord( 'postType', 'feedback', response.id, {
is_unread: true,
} );

// Revert the change in the sidebar
if ( response.status === 'publish' ) {
updateMenuCounterOptimistically( 1 );
}
} );
}, [ response, editEntityRecord, hasMarkedSelfAsRead ] );

Expand Down
53 changes: 46 additions & 7 deletions projects/packages/forms/src/dashboard/inbox/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { formatNumber } from '@automattic/number-formatters';

// Function to get the URL of the page or post where the form was submitted.
export const getPath = item => {
try {
Expand All @@ -9,20 +11,57 @@ export const getPath = item => {
};

/**
* Update the unread count in the admin menu.
* Update `count-0` style CSS class in the unread menu badge with new count like `count-1`.
*
* @param {HTMLElement} element - Counter badge element
* @param {number} count - Count to use in new CSS class
*/
function updateBadge( element, count ) {
const oldClass = [ ...element.classList ].find( c => c.startsWith( 'count-' ) );
if ( oldClass ) {
element.classList.replace( oldClass, `count-${ count }` );
} else {
element.classList.add( `count-${ count }` );
}

element.ariaHidden = count > 0 ? 'false' : 'true';
element.textContent = formatNumber( count );
}

/**
* Update the unread count in the admin menu to specific count.
*
* @param {number} count - The new unread count.
*/
export const updateMenuCounter = count => {
// iterate over all elements with the class 'jp-feedback-unread-counter' and update their text content
// Iterate over all badges with the class 'jp-feedback-unread-counter' and update their count
document.querySelectorAll( '.jp-feedback-unread-counter' ).forEach( item => {
// Jetpack menu item has combined count and forms unread counter
if ( item.dataset.unreadDiff ) {
const newCount = parseInt( item.dataset.unreadDiff, 10 ) + count;
item.textContent = newCount > 0 ? newCount : '';
item.style.display = newCount > 0 ? '' : 'none';
const unreadDiff = parseInt( item.dataset.unreadDiff, 10 ) + count;
updateBadge( item, unreadDiff );
} else {
item.textContent = count > 0 ? count : '';
item.style.display = count > 0 ? '' : 'none';
updateBadge( item, count );
}
} );
};

/**
* Update the unread count in the admin menu by addition or substraction, not by knowing the actual count.
*
* @param {number} count - By how much we should add or substract from the current sidebar menu count; either positive or negative integer.
*/
export const updateMenuCounterOptimistically = count => {
// Iterate over all badges with the class 'jp-feedback-unread-counter' and update their count
document.querySelectorAll( '.jp-feedback-unread-counter' ).forEach( item => {
let optimisticCount = 0;
if ( item.textContent !== '' ) {
// Ensure large formatted numbers like "1,000" are converted to integers properly
optimisticCount = parseInt( item.textContent.replace( /\D/g, '' ), 10 ) + count;
}

if ( optimisticCount >= 0 ) {
updateBadge( item, optimisticCount );
}
} );
};
Loading