Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e34c8c2
Collab Sidebar: Replace AddCommentToolbarButton with CommentAvatarInd…
yashjawale Aug 20, 2025
1f2a1db
refactor: use threadParticipants and enhance avatar styling for origi…
yashjawale Aug 20, 2025
73400a9
refactor: simplify comment avatar styling and improve background colo…
yashjawale Aug 20, 2025
d8cf981
refactor: update comment avatar styles to use border colors instead o…
yashjawale Aug 21, 2025
2e82c39
refactor: update comment resolution logic and simplify unresolved cou…
yashjawale Aug 21, 2025
2520dfd
refactor: filter out trashed comments in thread participant retrieval
yashjawale Aug 21, 2025
b286b3f
fix: handle case of 100+ comments
yashjawale Aug 29, 2025
58e11a8
refactor: update method for retrieving total pages of comments
yashjawale Aug 29, 2025
db5c16c
fix: style overflow indicator for longer text
yashjawale Aug 29, 2025
d9bf2ae
fix: sentence comments end with period
yashjawale Aug 29, 2025
d46bbae
fix: handle case where main comment is not found in thread
yashjawale Aug 29, 2025
e9cc58e
refactor: remove unused AddCommentToolbarButton component
yashjawale Aug 29, 2025
970dda7
fix: remove title attribute from avatar
yashjawale Sep 11, 2025
ff766e4
fix: overflowText i18n
yashjawale Sep 11, 2025
9f5f11a
fix: overflowTitle i18n
yashjawale Sep 11, 2025
237d7aa
fix: use SCSS variables in styles
yashjawale Sep 11, 2025
0425317
fix: remove unused `all-resolved` class
yashjawale Sep 11, 2025
b9defa9
fix: comment-indicator-toolbar.js syntax
yashjawale Sep 11, 2025
342f655
style: fix style.scss formatting
yashjawale Sep 11, 2025
24edb96
refactor: style.scss use base variables
yashjawale Sep 12, 2025
43bb5a3
refactor: optimize comment thread handling in CollabSidebar
yashjawale Sep 12, 2025
22ab72b
Merge branch 'trunk' into feat/comment-toolbar-indicator
yashjawale Sep 12, 2025
b33eb7b
fix: comment 100+ indicator
yashjawale Sep 12, 2025
0f4d335
fix: unused postStatus variable
yashjawale Sep 12, 2025
65ea1fd
fix: irregular hover background on indicator
yashjawale Sep 12, 2025
c11a5c8
refactor: simplify post and comment data retrieval in CollabSidebar u…
yashjawale Sep 12, 2025
71df444
fix: exclude trashed block comments from query
yashjawale Sep 12, 2025
089d6fb
fix: allow multiple comment statuses in REST API requests and simplif…
yashjawale Sep 15, 2025
7d42184
fix: EOL whitespace in block-comments.php
yashjawale Sep 15, 2025
97877fd
Merge branch 'trunk' into feat/comment-toolbar-indicator
yashjawale Sep 15, 2025
7b86a9a
fix: remove function_exists check for gutenberg_filter_rest_comment_c…
yashjawale Sep 15, 2025
e48d5ad
fix: remove memoization from query arguments
yashjawale Sep 15, 2025
d42b3c4
Merge branch 'trunk' into feat/comment-toolbar-indicator
yashjawale Sep 15, 2025
6bff632
Merge branch 'trunk' into feat/comment-toolbar-indicator
yashjawale Sep 15, 2025
781e4d6
Refactor: Remove support for multiple comment statuses in REST API re…
yashjawale Sep 15, 2025
88f1954
Update packages/editor/src/components/collab-sidebar/comment-indicato…
t-hamano Sep 16, 2025
c2080f1
Update packages/editor/src/components/collab-sidebar/comment-indicato…
t-hamano Sep 16, 2025
a8acf34
Update packages/editor/src/components/collab-sidebar/comment-indicato…
t-hamano Sep 16, 2025
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* WordPress dependencies
*/
import { ToolbarButton } from '@wordpress/components';
import { _x, __, sprintf } from '@wordpress/i18n';
import { useMemo } from '@wordpress/element';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';

/**
* External dependencies
*/
import clsx from 'clsx';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';

const { CommentIconToolbarSlotFill } = unlock( blockEditorPrivateApis );

const CommentAvatarIndicator = ( { onClick, thread, hasMoreComments } ) => {
const threadParticipants = useMemo( () => {
if ( ! thread ) {
return [];
}

const participantsMap = new Map();
const allComments = [ thread, ...thread.reply ];

// Sort by date to show participants in chronological order.
allComments.sort( ( a, b ) => new Date( a.date ) - new Date( b.date ) );

allComments.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 === thread.id,
date: comment.date,
} );
}
}
} );

return Array.from( participantsMap.values() );
}, [ thread ] );

const hasUnresolved = thread?.status !== 'approved';

// Check if this specific thread has more participants due to pagination.
// If we have pagination AND this thread + its replies equals or exceeds the API limit,
// then this thread likely has more participants that weren't loaded.
const threadHasMoreParticipants =
hasMoreComments && thread?.reply && 1 + thread.reply.length >= 100;

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 =
threadHasMoreParticipants && overflowCount > 0
? __( '100+' )
: sprintf(
// translators: %s: Number of comments.
__( '+%s' ),
overflowCount
);

const overflowTitle =
threadHasMoreParticipants && overflowCount > 0
? __( '100+ participants' )
: sprintf(
// translators: %s: Number of comments.
__( '+%s more participants' ),
overflowCount
);

return (
<CommentIconToolbarSlotFill.Fill>
<ToolbarButton
className={ clsx( 'comment-avatar-indicator', {
'has-unresolved': hasUnresolved,
} ) }
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 } }
/>
) ) }
{ overflowCount > 0 && (
<div
className="comment-avatar-overflow"
style={ { zIndex: 0 } }
title={ overflowTitle }
>
{ overflowText }
</div>
) }
</div>
</ToolbarButton>
</CommentIconToolbarSlotFill.Fill>
);
};

export default CommentAvatarIndicator;
73 changes: 44 additions & 29 deletions packages/editor/src/components/collab-sidebar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { useState, useMemo } from '@wordpress/element';
import { comment as commentIcon } from '@wordpress/icons';
import { addFilter } from '@wordpress/hooks';
import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore, useEntityBlockEditor } from '@wordpress/core-data';
import {
store as coreStore,
useEntityBlockEditor,
useEntityRecords,
} from '@wordpress/core-data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as interfaceStore } from '@wordpress/interface';

Expand All @@ -25,7 +29,7 @@ import { Comments } from './comments';
import { AddComment } from './add-comment';
import { store as editorStore } from '../../store';
import AddCommentButton from './comment-button';
import AddCommentToolbarButton from './comment-button-toolbar';
import CommentAvatarIndicator from './comment-indicator-toolbar';
import { useGlobalStylesContext } from '../global-styles-provider';
import { getCommentIdsFromBlocks } from './utils';

Expand Down Expand Up @@ -80,7 +84,7 @@ function CollabSidebarContent( {
comment_approved: 0,
};

// Create a new object, conditionally including the parent property
// Create a new object, conditionally including the parent property.
const updatedArgs = {
...args,
...( parentCommentId ? { parent: parentCommentId } : {} ),
Expand Down Expand Up @@ -225,25 +229,29 @@ export default function CollabSidebar() {
const { enableComplementaryArea } = useDispatch( interfaceStore );
const { getActiveComplementaryArea } = useSelect( interfaceStore );

const { postId, postType, threads } = useSelect( ( select ) => {
const { postId, postType } = useSelect( ( select ) => {
const { getCurrentPostId, getCurrentPostType } = select( editorStore );
const _postId = getCurrentPostId();
const data =
!! _postId && typeof _postId === 'number'
? select( coreStore ).getEntityRecords( 'root', 'comment', {
post: _postId,
type: 'block_comment',
status: 'any',
per_page: 100,
} )
: null;
return {
postId: _postId,
postId: getCurrentPostId(),
postType: getCurrentPostType(),
threads: data,
};
}, [] );

const queryArgs = {
post: postId,
type: 'block_comment',
status: 'all',
per_page: 100,
};

const { records: threads, totalPages } = useEntityRecords(
'root',
'comment',
queryArgs
);

const hasMoreComments = totalPages && totalPages > 1;

const { blockCommentId } = useSelect( ( select ) => {
const { getBlockAttributes, getSelectedBlockClientId } =
select( blockEditorStore );
Expand All @@ -265,28 +273,26 @@ export default function CollabSidebar() {
id: postId,
} );

// Process comments to build the tree structure
// Process comments to build the tree structure.
const { resultComments, unresolvedSortedThreads } = useMemo( () => {
// Create a compare to store the references to all objects by id
// Create a compare to store the references to all objects by id.
const compare = {};
const result = [];

const filteredComments = ( threads ?? [] ).filter(
( comment ) => comment.status !== 'trash'
);
const allComments = threads ?? [];

// Initialize each object with an empty `reply` array
filteredComments.forEach( ( item ) => {
// Initialize each object with an empty `reply` array.
allComments.forEach( ( item ) => {
compare[ item.id ] = { ...item, reply: [] };
} );

// Iterate over the data to build the tree structure
filteredComments.forEach( ( item ) => {
// Iterate over the data to build the tree structure.
allComments.forEach( ( item ) => {
if ( item.parent === 0 ) {
// If parent is 0, it's a root item, push it to the result array
// If parent is 0, it's a root item, push it to the result array.
result.push( compare[ item.id ] );
} else if ( compare[ item.parent ] ) {
// Otherwise, find its parent and push it to the parent's `reply` array
// Otherwise, find its parent and push it to the parent's `reply` array.
compare[ item.parent ].reply.push( compare[ item.id ] );
}
} );
Expand Down Expand Up @@ -336,17 +342,26 @@ export default function CollabSidebar() {
}

const AddCommentComponent = blockCommentId
? AddCommentToolbarButton
? CommentAvatarIndicator
: AddCommentButton;

// Find the current thread for the selected block.
const currentThread = blockCommentId
? resultComments.find( ( thread ) => thread.id === blockCommentId )
: null;

// If postId is not a valid number, do not render the comment sidebar.
if ( ! ( !! postId && typeof postId === 'number' ) ) {
return null;
}

return (
<>
<AddCommentComponent onClick={ openCollabBoard } />
<AddCommentComponent
onClick={ openCollabBoard }
thread={ currentThread }
hasMoreComments={ hasMoreComments }
/>
<PluginSidebar
identifier={ collabHistorySidebarName }
// translators: Comments sidebar title
Expand Down
76 changes: 76 additions & 0 deletions packages/editor/src/components/collab-sidebar/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,79 @@
padding: 0;
}
}

// Comment avatar indicators.
.comment-avatar-indicator {
position: relative;
padding: 4px;
min-width: auto;
background: transparent;
border: none;

&:hover::before {
background: rgba(0, 0, 0, 0.04);
}

&.has-unresolved {
.comment-avatar-stack {
&::after {
content: "";
position: absolute;
top: -2px;
right: -2px;
width: $grid-unit-10;
height: $grid-unit-10;
background: #d63638;
border-radius: $radius-round;
border: $border-width solid $white;
z-index: 10;
}
}
}
}

.comment-avatar-stack {
display: flex;
align-items: center;
position: relative;
height: $icon-size;
}

.comment-avatar {
width: $icon-size;
height: $icon-size;
border-radius: $radius-round;
border: 2px solid $white;
margin-left: -6px;
flex-shrink: 0;

&:first-child {
margin-left: 0;
border-color: #de6e55;
}

&:nth-child(2) {
border-color: #599637;
}

&:nth-child(3) {
border-color: #3858e9;
}
}

.comment-avatar-overflow {
width: fit-content;
height: $icon-size;
border-radius: 4rem;
padding: 0 4px;
background: #757575;
color: $white;
border: 2px solid $white;
margin-left: -6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 600;
flex-shrink: 0;
}
Loading