Skip to content

Commit

Permalink
Merge pull request #21781 from Yoast/21724-new-general-page-updating-…
Browse files Browse the repository at this point in the history
…counters-properly-new

21724 new general page updating counters properly new
  • Loading branch information
leonidasmi authored Oct 30, 2024
2 parents a57839d + f4d6063 commit ab9fb14
Show file tree
Hide file tree
Showing 12 changed files with 851 additions and 80 deletions.
11 changes: 6 additions & 5 deletions inc/class-wpseo-admin-bar-menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -855,14 +855,15 @@ protected function get_notification_counter() {
$notification_center = Yoast_Notification_Center::get();
$notification_count = $notification_center->get_notification_count();

if ( ! $notification_count ) {
return '';
}

/* translators: Hidden accessibility text; %s: number of notifications. */
$counter_screen_reader_text = sprintf( _n( '%s notification', '%s notifications', $notification_count, 'wordpress-seo' ), number_format_i18n( $notification_count ) );

return sprintf( ' <div class="wp-core-ui wp-ui-notification yoast-issue-counter"><span class="yoast-issues-count" aria-hidden="true">%d</span><span class="screen-reader-text">%s</span></div>', $notification_count, $counter_screen_reader_text );
return sprintf(
' <div class="wp-core-ui wp-ui-notification yoast-issue-counter%s"><span class="yoast-issues-count" aria-hidden="true">%d</span><span class="screen-reader-text">%s</span></div>',
( $notification_count ) ? '' : ' yst-hidden',
$notification_count,
$counter_screen_reader_text
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import apiFetch from "@wordpress/api-fetch";
import { useDispatch } from "@wordpress/data";
import { useCallback, useReducer, useState, useEffect } from "@wordpress/element";
import { __ } from "@wordpress/i18n";
import { uniq } from "lodash";

import { STORE_NAME } from "../general/constants";
import { configurationReducer } from "./tailwind-components/helpers/index.js";
import SocialProfilesStep from "./tailwind-components/steps/social-profiles/social-profiles-step";
import Stepper, { Step } from "./tailwind-components/stepper";
Expand Down Expand Up @@ -158,6 +159,7 @@ function calculateInitialState( windowObject, isStepFinished ) {
* @returns {WPElement} The FirstTimeConfigurationSteps component.
*/
export default function FirstTimeConfigurationSteps() {
const { removeAlert } = useDispatch( STORE_NAME );
const [ finishedSteps, setFinishedSteps ] = useState( window.wpseoFirstTimeConfigurationData.finishedSteps );

const isStepFinished = useCallback( ( stepId ) => {
Expand Down Expand Up @@ -192,32 +194,10 @@ export default function FirstTimeConfigurationSteps() {
without triggering a reload, whereas the window variable remains stale. */
useEffect( () => {
if ( indexingState === "completed" ) {
const indexationNotice = document.getElementById( "wpseo-reindex" );
if ( indexationNotice ) {
const allCounters = document.querySelectorAll( ".yoast-issue-counter, #toplevel_page_wpseo_dashboard .update-plugins" );

// Update the notification counters if there are any (non-zero ones).
if ( allCounters.length > 0 && allCounters[ 0 ].firstChild.textContent !== "0" ) {
// Get the oldCount for easier targeting.
const oldCount = allCounters[ 0 ].firstChild.textContent;
const newCount = ( parseInt( oldCount, 10 ) - 1 ).toString();
allCounters.forEach( ( counterNode => {
// The classList replace will return false if the class was not present (and thus an adminbar counter).
const isAdminBarCounter = ! counterNode.classList.replace( "count-" + oldCount, "count-" + newCount );
// If the count reaches zero because of this, remove the red dot alltogether.
if ( isAdminBarCounter && newCount === "0" ) {
counterNode.style.display = "none";
} else {
counterNode.firstChild.textContent = counterNode.firstChild.textContent.replace( oldCount, newCount );
counterNode.lastChild.textContent = counterNode.lastChild.textContent.replace( oldCount, newCount );
}
} ) );
}
indexationNotice.remove();
}
removeAlert( "wpseo-reindex" );
window.yoastIndexingData.amount = "0";
}
}, [ indexingState ] );
}, [ indexingState, removeAlert ] );

const isStep2Finished = isStepFinished( STEPS.siteRepresentation );
const isStep3Finished = isStepFinished( STEPS.socialProfiles );
Expand Down
2 changes: 2 additions & 0 deletions packages/js/src/general/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getMigratingNoticeInfo, deleteMigratingNotices } from "../helpers/migra
import Notice from "./components/notice";
import WebinarPromoNotification from "../components/WebinarPromoNotification";
import { shouldShowWebinarPromotionNotificationInDashboard } from "../helpers/shouldShowWebinarPromotionNotification";
import { useNotificationCountSync } from "./hooks/use-notification-count-sync";
import { STEPS as FTC_STEPS } from "../first-time-configuration/constants";

/**
Expand Down Expand Up @@ -79,6 +80,7 @@ const App = () => {
const { pathname } = useLocation();
const alertToggleError = useSelectGeneralPage( "selectAlertToggleError", [], [] );
const { setAlertToggleError } = useDispatch( STORE_NAME );
useNotificationCountSync();

const handleDismiss = useCallback( () => {
setAlertToggleError( null );
Expand Down
2 changes: 1 addition & 1 deletion packages/js/src/general/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@

export { useNotificationCountSync } from "./use-notification-count-sync";
export { default as useSelectGeneralPage } from "./use-select-general-page";
16 changes: 16 additions & 0 deletions packages/js/src/general/hooks/use-notification-count-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useSelect } from "@wordpress/data";
import { useEffect } from "@wordpress/element";
import { updateNotificationsCount } from "../../shared-admin/helpers";
import { STORE_NAME } from "../constants";

/**
* Sync the notification count with the Yoast menu and admin bar.
* @returns {void}
*/
export const useNotificationCountSync = () => {
const activeAlertCount = useSelect( ( select ) => select( STORE_NAME ).selectActiveAlertsCount(), [] );

useEffect( () => {
updateNotificationsCount( activeAlertCount );
}, [ activeAlertCount ] );
};
51 changes: 38 additions & 13 deletions packages/js/src/general/store/alert-center.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createSlice, createSelector } from "@reduxjs/toolkit";
import { ASYNC_ACTION_NAMES } from "../../shared-admin/constants";
import { createSelector, createSlice } from "@reduxjs/toolkit";
import { select } from "@wordpress/data";
import { STORE_NAME } from "../constants";
import { get } from "lodash";
import { ASYNC_ACTION_NAMES } from "../../shared-admin/constants";
import { STORE_NAME } from "../constants";

export const ALERT_CENTER_NAME = "alertCenter";

Expand All @@ -14,16 +14,17 @@ const TOGGLE_ALERT_VISIBILITY = "toggleAlertVisibility";
* @param {boolean} hidden The hidden state of the alert.
* @returns {Object} Success or error action object.
*/
export function* toggleAlertStatus( id, nonce, hidden = false ) {
function* toggleAlertStatus( id, nonce, hidden = false ) {
yield{ type: `${ TOGGLE_ALERT_VISIBILITY }/${ ASYNC_ACTION_NAMES.request }` };
try {
yield{ type: TOGGLE_ALERT_VISIBILITY,
yield{
type: TOGGLE_ALERT_VISIBILITY,
payload: {
id,
nonce,
hidden,
},
};
};
return { type: `${ TOGGLE_ALERT_VISIBILITY }/${ ASYNC_ACTION_NAMES.success }`, payload: { id } };
} catch ( error ) {
return { type: `${ TOGGLE_ALERT_VISIBILITY }/${ ASYNC_ACTION_NAMES.error }`, payload: { id } };
Expand Down Expand Up @@ -68,6 +69,14 @@ const slice = createSlice( {
reducers: {
toggleAlert,
setAlertToggleError,
/**
* @param {Object} state The state of the slice.
* @param {string} id The ID of the alert to remove.
* @returns {void}
*/
removeAlert( state, { payload: id } ) {
state.alerts = state.alerts.filter( ( alert ) => alert.id !== id );
},
},
extraReducers: ( builder ) => {
builder.addCase( `${ TOGGLE_ALERT_VISIBILITY }/${ ASYNC_ACTION_NAMES.success }`, ( state, { payload: { id } } ) => {
Expand All @@ -90,34 +99,50 @@ export const getInitialAlertCenterState = slice.getInitialState;
* @param {object} state The state.
* @returns {array} The alerts.
*/
const selectAlerts = ( state ) => get( state, `${ALERT_CENTER_NAME}.alerts`, [] );
const selectAlerts = ( state ) => get( state, `${ ALERT_CENTER_NAME }.alerts`, [] );

const selectActiveAlerts = createSelector(
[ selectAlerts ],
( alerts ) => alerts.filter( ( alert ) => ! alert.dismissed )
);

/**
* Selector to get the alert toggle error.
*
* @param {object} state The state.
* @returns {string} id The id of the alert which caused the error..
*/
const selectAlertToggleError = ( state ) => get( state, `${ALERT_CENTER_NAME}.alertToggleError`, null );
const selectAlertToggleError = ( state ) => get( state, `${ ALERT_CENTER_NAME }.alertToggleError`, null );

export const alertCenterSelectors = {
selectActiveProblems: createSelector(
[ selectAlerts ],
( alerts ) => alerts.filter( ( alert ) => alert.type === "error" && ! alert.dismissed )
[ selectActiveAlerts ],
( alerts ) => alerts.filter( ( alert ) => alert.type === "error" )
),
selectDismissedProblems: createSelector(
[ selectAlerts ],
( alerts ) => alerts.filter( ( alert ) => alert.type === "error" && alert.dismissed )
),
selectActiveNotifications: createSelector(
[ selectAlerts ],
( alerts ) => alerts.filter( ( alert ) => alert.type === "warning" && ! alert.dismissed )
[ selectActiveAlerts ],
( alerts ) => alerts.filter( ( alert ) => alert.type === "warning" )
),
selectDismissedNotifications: createSelector(
[ selectAlerts ],
( alerts ) => alerts.filter( ( alert ) => alert.type === "warning" && alert.dismissed )
),
selectAlertToggleError,
selectAlert: createSelector(
[
selectAlerts,
( state, id ) => id,
],
( alerts, id ) => alerts.find( ( alert ) => alert.id === id )
),
selectActiveAlertsCount: createSelector(
[ selectActiveAlerts ],
( alerts ) => alerts.length
),
};

export const alertCenterActions = {
Expand Down Expand Up @@ -148,4 +173,4 @@ export const alertCenterControls = {
},
};

export default slice.reducer;
export const alertCenterReducer = slice.reducer;
4 changes: 2 additions & 2 deletions packages/js/src/general/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { STORE_NAME } from "../constants";
import preferences, { createInitialPreferencesState, preferencesActions, preferencesSelectors } from "./preferences";
import { reducers, selectors, actions } from "@yoast/externals/redux";
import * as dismissedAlertsControls from "../../redux/controls/dismissedAlerts";
import alertCenter, { alertCenterActions, alertCenterSelectors, getInitialAlertCenterState, alertCenterControls, ALERT_CENTER_NAME } from "./alert-center";
import { alertCenterReducer, alertCenterActions, alertCenterSelectors, getInitialAlertCenterState, alertCenterControls, ALERT_CENTER_NAME } from "./alert-center";

const { currentPromotions, dismissedAlerts, isPremium } = reducers;
const { isAlertDismissed, getIsPremium, isPromotionActive } = selectors;
Expand Down Expand Up @@ -50,7 +50,7 @@ const createStore = ( { initialState } ) => {
reducer: combineReducers( {
[ LINK_PARAMS_NAME ]: linkParamsReducer,
preferences,
[ ALERT_CENTER_NAME ]: alertCenter,
[ ALERT_CENTER_NAME ]: alertCenterReducer,
currentPromotions,
dismissedAlerts,
isPremium,
Expand Down
1 change: 1 addition & 0 deletions packages/js/src/shared-admin/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { fixWordPressMenuScrolling } from "./fix-wordpress-menu-scrolling";
export { updateNotificationsCount } from "./notifications-count";
41 changes: 41 additions & 0 deletions packages/js/src/shared-admin/helpers/notifications-count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { _n, sprintf } from "@wordpress/i18n";

/**
* Update the text content of an element if it exists.
* @param {HTMLElement} root The root element.
* @param {string} selector The selector.
* @param {string} text The text.
* @returns {HTMLElement|null} The element or null.
*/
const updateTextContentIfElementExists = ( root, selector, text ) => {
const element = root.querySelector( selector );
if ( element ) {
element.textContent = text;
}
return element;
};

/**
* Update the notification total in the Yoast SEO menu and the admin bar badges.
* @param {number} total The total number of notifications.
* @returns {void}
*/
export const updateNotificationsCount = ( total ) => {
// Note: these translation is the same as on the server side.
/* translators: Hidden accessibility text; %s: number of notifications. */
const screenReaderText = sprintf( _n( "%s notification", "%s notifications", total, "wordpress-seo" ), total );

const menuItems = document.querySelectorAll( "#toplevel_page_wpseo_dashboard .update-plugins" );
for ( const menuItem of menuItems ) {
menuItem.className = `update-plugins count-${ total }`;
updateTextContentIfElementExists( menuItem, ".plugin-count", String( total ) );
updateTextContentIfElementExists( menuItem, ".screen-reader-text", screenReaderText );
}

const adminBarItems = document.querySelectorAll( "#wp-admin-bar-wpseo-menu .yoast-issue-counter" );
for ( const adminBar of adminBarItems ) {
adminBar.classList.toggle( "yst-hidden", total === 0 );
updateTextContentIfElementExists( adminBar, ".yoast-issues-count", String( total ) );
updateTextContentIfElementExists( adminBar, ".screen-reader-text", screenReaderText );
}
};
Loading

0 comments on commit ab9fb14

Please sign in to comment.