diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js
index 54ba4cd7eec55a..05a8ff8691ea6b 100644
--- a/packages/edit-post/src/index.js
+++ b/packages/edit-post/src/index.js
@@ -73,6 +73,7 @@ export function initializeEditor(
enableChoosePatternModal: true,
isPublishSidebarEnabled: true,
showCollaborationCursor: false,
+ showCollaborationNotifications: true,
} );
if ( window.__clientSideMediaProcessing ) {
diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js
index d9530785195787..33fcccf4963ca8 100644
--- a/packages/edit-site/src/index.js
+++ b/packages/edit-site/src/index.js
@@ -74,6 +74,7 @@ export function initializeEditor( id, settings ) {
showListViewByDefault: false,
enableChoosePatternModal: true,
showCollaborationCursor: false,
+ showCollaborationNotifications: true,
} );
if ( window.__clientSideMediaProcessing ) {
diff --git a/packages/editor/src/components/collaborators-presence/test/use-collaborator-notifications.ts b/packages/editor/src/components/collaborators-presence/test/use-collaborator-notifications.ts
index b00b04c872b934..2d22aa2b72ee8e 100644
--- a/packages/editor/src/components/collaborators-presence/test/use-collaborator-notifications.ts
+++ b/packages/editor/src/components/collaborators-presence/test/use-collaborator-notifications.ts
@@ -19,9 +19,13 @@ const mockCreateNotice = jest.fn();
let mockOnJoinCallback: Function | null = null;
let mockOnLeaveCallback: Function | null = null;
let mockOnPostSaveCallback: Function | null = null;
+let lastJoinPostId: unknown;
+let lastLeavePostId: unknown;
+let lastSavePostId: unknown;
let mockEditorState = {
postStatus: 'draft',
isCollaborationEnabled: true,
+ showCollaborationNotifications: true,
};
jest.mock( '@wordpress/data', () => ( {
@@ -33,6 +37,10 @@ jest.mock( '@wordpress/notices', () => ( {
store: 'core/notices',
} ) );
+jest.mock( '@wordpress/preferences', () => ( {
+ store: 'core/preferences',
+} ) );
+
// Mock the editor store to prevent deep import chain (blocks, rich-text, etc.)
jest.mock( '../../../store', () => ( {
store: 'core/editor',
@@ -46,17 +54,20 @@ jest.mock( '@wordpress/core-data', () => ( {
jest.mock( '../../../lock-unlock', () => ( {
unlock: jest.fn( () => ( {
useOnCollaboratorJoin: jest.fn(
- ( _postId: unknown, _postType: unknown, callback: Function ) => {
+ ( postId: unknown, _postType: unknown, callback: Function ) => {
+ lastJoinPostId = postId;
mockOnJoinCallback = callback;
}
),
useOnCollaboratorLeave: jest.fn(
- ( _postId: unknown, _postType: unknown, callback: Function ) => {
+ ( postId: unknown, _postType: unknown, callback: Function ) => {
+ lastLeavePostId = postId;
mockOnLeaveCallback = callback;
}
),
useOnPostSave: jest.fn(
- ( _postId: unknown, _postType: unknown, callback: Function ) => {
+ ( postId: unknown, _postType: unknown, callback: Function ) => {
+ lastSavePostId = postId;
mockOnPostSaveCallback = callback;
}
),
@@ -103,21 +114,40 @@ function makeMe( overrides: Record< string, unknown > = {} ) {
// --- Setup ---
function buildMockSelect() {
- return () => ( {
- getCurrentPostAttribute: ( attr: string ) =>
- attr === 'status' ? mockEditorState.postStatus : undefined,
- isCollaborationEnabledForCurrentPost: () =>
- mockEditorState.isCollaborationEnabled,
- } );
+ return ( storeKey: string ) => {
+ if ( storeKey === 'core/preferences' ) {
+ return {
+ get: ( scope: string, name: string ) => {
+ if (
+ scope === 'core' &&
+ name === 'showCollaborationNotifications'
+ ) {
+ return mockEditorState.showCollaborationNotifications;
+ }
+ return undefined;
+ },
+ };
+ }
+ return {
+ getCurrentPostAttribute: ( attr: string ) =>
+ attr === 'status' ? mockEditorState.postStatus : undefined,
+ isCollaborationEnabledForCurrentPost: () =>
+ mockEditorState.isCollaborationEnabled,
+ };
+ };
}
beforeEach( () => {
mockOnJoinCallback = null;
mockOnLeaveCallback = null;
mockOnPostSaveCallback = null;
+ lastJoinPostId = undefined;
+ lastLeavePostId = undefined;
+ lastSavePostId = undefined;
mockEditorState = {
postStatus: 'draft',
isCollaborationEnabled: true,
+ showCollaborationNotifications: true,
};
mockCreateNotice.mockClear();
( useSelect as jest.Mock ).mockImplementation( ( selector: Function ) =>
@@ -337,4 +367,30 @@ describe( 'useCollaboratorNotifications', () => {
expect( mockCreateNotice ).not.toHaveBeenCalled();
} );
} );
+
+ describe( 'when notifications are disabled', () => {
+ it( 'passes null postId to hooks when showCollaborationNotifications preference is false', () => {
+ mockEditorState = {
+ ...mockEditorState,
+ showCollaborationNotifications: false,
+ };
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
+
+ expect( lastJoinPostId ).toBeNull();
+ expect( lastLeavePostId ).toBeNull();
+ expect( lastSavePostId ).toBeNull();
+ } );
+
+ it( 'passes null postId to hooks when collaboration is disabled', () => {
+ mockEditorState = {
+ ...mockEditorState,
+ isCollaborationEnabled: false,
+ };
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
+
+ expect( lastJoinPostId ).toBeNull();
+ expect( lastLeavePostId ).toBeNull();
+ expect( lastSavePostId ).toBeNull();
+ } );
+ } );
} );
diff --git a/packages/editor/src/components/collaborators-presence/use-collaborator-notifications.ts b/packages/editor/src/components/collaborators-presence/use-collaborator-notifications.ts
index 0d79d57585df30..10c11e6dcf4ae3 100644
--- a/packages/editor/src/components/collaborators-presence/use-collaborator-notifications.ts
+++ b/packages/editor/src/components/collaborators-presence/use-collaborator-notifications.ts
@@ -10,6 +10,7 @@ import {
type PostEditorAwarenessState,
type PostSaveEvent,
} from '@wordpress/core-data';
+import { store as preferencesStore } from '@wordpress/preferences';
/**
* Internal dependencies
@@ -30,16 +31,6 @@ const NOTIFICATION_TYPE = {
COLLAB_USER_EXITED: 'collab-user-exited',
} as const;
-/**
- * Development kill-switches for each notification type. Flip to `false`
- * to suppress a category during local testing without removing code.
- */
-const NOTIFICATIONS_CONFIG = {
- userEntered: true,
- userExited: true,
- postUpdated: true,
-};
-
const PUBLISHED_STATUSES = [ 'publish', 'private', 'future' ];
/**
@@ -77,23 +68,32 @@ export function useCollaboratorNotifications(
postId: number | null,
postType: string | null
): void {
- const { postStatus, isCollaborationEnabled } = useSelect( ( select ) => {
- const editorSel = select( editorStore );
- return {
- postStatus: editorSel.getCurrentPostAttribute( 'status' ) as
- | string
- | undefined,
- isCollaborationEnabled:
- editorSel.isCollaborationEnabledForCurrentPost(),
- };
- }, [] );
+ const { postStatus, isCollaborationEnabled, showNotifications } = useSelect(
+ ( select ) => {
+ const editorSel = select( editorStore );
+ return {
+ postStatus: editorSel.getCurrentPostAttribute( 'status' ) as
+ | string
+ | undefined,
+ isCollaborationEnabled:
+ editorSel.isCollaborationEnabledForCurrentPost(),
+ showNotifications:
+ select( preferencesStore ).get(
+ 'core',
+ 'showCollaborationNotifications'
+ ) ?? true,
+ };
+ },
+ []
+ );
const { createNotice } = useDispatch( noticesStore );
- // Pass null when collaboration is disabled to prevent the hooks
- // from subscribing to awareness state.
- const effectivePostId = isCollaborationEnabled ? postId : null;
- const effectivePostType = isCollaborationEnabled ? postType : null;
+ // Pass null when collaboration is disabled or notifications are
+ // turned off to prevent the hooks from subscribing to awareness state.
+ const shouldSubscribe = isCollaborationEnabled && showNotifications;
+ const effectivePostId = shouldSubscribe ? postId : null;
+ const effectivePostType = shouldSubscribe ? postType : null;
useOnCollaboratorJoin(
effectivePostId,
@@ -103,10 +103,6 @@ export function useCollaboratorNotifications(
collaborator: PostEditorAwarenessState,
me?: PostEditorAwarenessState
) => {
- if ( ! NOTIFICATIONS_CONFIG.userEntered ) {
- return;
- }
-
/*
* Skip collaborators who were present before the current user
* joined. Their enteredAt is earlier than ours, meaning we're
@@ -143,10 +139,6 @@ export function useCollaboratorNotifications(
effectivePostType,
useCallback(
( collaborator: PostEditorAwarenessState ) => {
- if ( ! NOTIFICATIONS_CONFIG.userExited ) {
- return;
- }
-
void createNotice(
'info',
sprintf(
@@ -174,7 +166,7 @@ export function useCollaboratorNotifications(
saver: PostEditorAwarenessState,
prevEvent: PostSaveEvent | null
) => {
- if ( ! NOTIFICATIONS_CONFIG.postUpdated || ! postStatus ) {
+ if ( ! postStatus ) {
return;
}
diff --git a/packages/editor/src/components/preferences-modal/index.js b/packages/editor/src/components/preferences-modal/index.js
index 60005156821f6a..a71afcf9dd658d 100644
--- a/packages/editor/src/components/preferences-modal/index.js
+++ b/packages/editor/src/components/preferences-modal/index.js
@@ -128,6 +128,16 @@ function PreferencesModalContents( { extraSections = {} } ) {
) }
label={ __( 'Show avatar in blocks' ) }
/>
+