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' ) } /> +