-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Block comments: Add comment indicators in the block toolbar #71271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
e34c8c2
1f2a1db
73400a9
d8cf981
2e82c39
2520dfd
b286b3f
58e11a8
db5c16c
d9bf2ae
d46bbae
e9cc58e
970dda7
ff766e4
9f5f11a
237d7aa
0425317
b9defa9
342f655
24edb96
43bb5a3
22ab72b
b33eb7b
0f4d335
65ea1fd
c11a5c8
71df444
089d6fb
7d42184
97877fd
7b86a9a
e48d5ad
d42b3c4
6bff632
781e4d6
88f1954
c2080f1
a8acf34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { ToolbarButton } from '@wordpress/components'; | ||
| import { _x } from '@wordpress/i18n'; | ||
| import { useSelect } from '@wordpress/data'; | ||
| import { | ||
| privateApis as blockEditorPrivateApis, | ||
| store as blockEditorStore, | ||
| } from '@wordpress/block-editor'; | ||
| import { store as coreStore } from '@wordpress/core-data'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { unlock } from '../../lock-unlock'; | ||
| import { store as editorStore } from '../../store'; | ||
|
|
||
| const { CommentIconToolbarSlotFill } = unlock( blockEditorPrivateApis ); | ||
|
|
||
| const CommentAvatarIndicator = ( { onClick } ) => { | ||
| const { threadParticipants, hasUnresolved, hasMoreComments } = useSelect( | ||
| ( select ) => { | ||
| const { getBlockAttributes, getSelectedBlockClientId } = | ||
| select( blockEditorStore ); | ||
| const { getCurrentPostId } = select( editorStore ); | ||
| const selectedBlock = getSelectedBlockClientId(); | ||
| const selectedBlockAttributes = selectedBlock | ||
| ? getBlockAttributes( selectedBlock ) | ||
| : null; | ||
| const postId = getCurrentPostId(); | ||
|
|
||
| // Get comment data for this block. | ||
| const blockCommentIdValue = selectedBlockAttributes?.blockCommentId; | ||
| const participantsMap = new Map(); | ||
| let isResolved = false; | ||
| let moreCommentsExist = false; | ||
|
|
||
| if ( blockCommentIdValue && postId ) { | ||
| const queryArgs = { | ||
| post: postId, | ||
| type: 'block_comment', | ||
| status: 'any', | ||
| per_page: 100, | ||
adamsilverstein marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| const comments = select( coreStore ).getEntityRecords( | ||
| 'root', | ||
| 'comment', | ||
| queryArgs | ||
| ); | ||
|
|
||
| // Check if there are more pages available. | ||
| const totalPages = select( | ||
| coreStore | ||
| ).getEntityRecordsTotalPages( 'root', 'comment', queryArgs ); | ||
|
|
||
| // If we have more than 1 page, there are more comments | ||
| if ( totalPages && totalPages > 1 ) { | ||
| moreCommentsExist = true; | ||
| } | ||
|
|
||
| if ( comments ) { | ||
| // Get all comments in this thread. | ||
| // Main comment has id === blockCommentIdValue | ||
| // Replies have parent === blockCommentIdValue | ||
| const threadComments = comments.filter( | ||
| ( comment ) => | ||
| comment.status !== 'trash' && | ||
| ( comment.id === blockCommentIdValue || | ||
| comment.parent === blockCommentIdValue ) | ||
| ); | ||
|
|
||
| // Sort by date to show participants in chronological order. | ||
| threadComments.sort( | ||
| ( a, b ) => new Date( a.date ) - new Date( b.date ) | ||
| ); | ||
|
|
||
| // Find the main thread comment (first comment). | ||
| const mainComment = threadComments.find( | ||
| ( comment ) => comment.id === blockCommentIdValue | ||
| ); | ||
|
|
||
| // If no main comment is found, the thread doesn't exist. | ||
| if ( ! mainComment ) { | ||
| return { | ||
| threadParticipants: [], | ||
| hasUnresolved: false, | ||
| hasMoreComments: false, | ||
| }; | ||
| } | ||
|
|
||
| // Thread is resolved if the main comment is approved. | ||
| isResolved = mainComment.status === 'approved'; | ||
|
|
||
| threadComments.forEach( ( comment ) => { | ||
| // Track thread participants (original commenter + repliers) | ||
| if ( | ||
| comment.author_name && | ||
| comment.author_avatar_urls | ||
| ) { | ||
| const authorKey = `${ comment.author }-${ comment.author_name }`; | ||
| if ( ! participantsMap.has( authorKey ) ) { | ||
| participantsMap.set( authorKey, { | ||
| name: comment.author_name, | ||
| avatar: | ||
| comment.author_avatar_urls?.[ '48' ] || | ||
|
||
| comment.author_avatar_urls?.[ '96' ], | ||
|
||
| isOriginalCommenter: | ||
| comment.id === blockCommentIdValue, | ||
| date: comment.date, | ||
| } ); | ||
| } | ||
| } | ||
| } ); | ||
| } | ||
| } | ||
|
|
||
| // Convert to array and maintain chronological order. | ||
| const participants = Array.from( participantsMap.values() ); | ||
|
|
||
| return { | ||
| threadParticipants: participants, | ||
| hasUnresolved: ! isResolved, | ||
| hasMoreComments: moreCommentsExist, | ||
| }; | ||
| }, | ||
| [] | ||
| ); | ||
|
|
||
| if ( ! threadParticipants.length ) { | ||
| return null; | ||
| } | ||
|
|
||
| // Show up to 3 avatars, with overflow indicator. | ||
| const maxAvatars = 3; | ||
| const visibleParticipants = threadParticipants.slice( 0, maxAvatars ); | ||
| const overflowCount = Math.max( 0, threadParticipants.length - maxAvatars ); | ||
|
|
||
| // If we hit the comment limit, show "100+" instead of exact overflow count. | ||
| const overflowText = | ||
| hasMoreComments && overflowCount > 0 ? '100+' : `+${ overflowCount }`; | ||
yashjawale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const overflowTitle = | ||
| hasMoreComments && overflowCount > 0 | ||
| ? '100+ participants' | ||
| : `+${ overflowCount } more participants`; | ||
yashjawale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return ( | ||
| <CommentIconToolbarSlotFill.Fill> | ||
| <ToolbarButton | ||
| className={ `comment-avatar-indicator ${ | ||
| hasUnresolved ? 'has-unresolved' : 'all-resolved' | ||
| }` } | ||
yashjawale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| label={ _x( 'View comments', 'View comment thread' ) } | ||
| onClick={ onClick } | ||
| showTooltip | ||
| > | ||
| <div className="comment-avatar-stack"> | ||
| { visibleParticipants.map( ( participant, index ) => ( | ||
| <img | ||
| key={ participant.name + index } | ||
| src={ participant.avatar } | ||
| alt={ participant.name } | ||
| className="comment-avatar" | ||
| style={ { zIndex: maxAvatars - index } } | ||
| title={ participant.name } | ||
yashjawale marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /> | ||
| ) ) } | ||
| { overflowCount > 0 && ( | ||
| <div | ||
| className="comment-avatar-overflow" | ||
| style={ { zIndex: 0 } } | ||
| title={ overflowTitle } | ||
| > | ||
| { overflowText } | ||
| </div> | ||
| ) } | ||
| </div> | ||
| </ToolbarButton> | ||
| </CommentIconToolbarSlotFill.Fill> | ||
| ); | ||
| }; | ||
|
|
||
| export default CommentAvatarIndicator; | ||

Uh oh!
There was an error while loading. Please reload this page.