@wordpress/editor 14.31.1-next.f56bd8138.0 → 14.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/collab-sidebar/add-comment.js +10 -4
  3. package/build/components/collab-sidebar/add-comment.js.map +1 -1
  4. package/build/components/collab-sidebar/comment-author-info.js +39 -11
  5. package/build/components/collab-sidebar/comment-author-info.js.map +1 -1
  6. package/build/components/collab-sidebar/comment-form.js +23 -19
  7. package/build/components/collab-sidebar/comment-form.js.map +1 -1
  8. package/build/components/collab-sidebar/comment-indicator-toolbar.js +7 -4
  9. package/build/components/collab-sidebar/comment-indicator-toolbar.js.map +1 -1
  10. package/build/components/collab-sidebar/comments.js +220 -169
  11. package/build/components/collab-sidebar/comments.js.map +1 -1
  12. package/build/components/collab-sidebar/hooks.js +96 -0
  13. package/build/components/collab-sidebar/hooks.js.map +1 -0
  14. package/build/components/collab-sidebar/index.js +79 -169
  15. package/build/components/collab-sidebar/index.js.map +1 -1
  16. package/build/components/collab-sidebar/utils.js +72 -25
  17. package/build/components/collab-sidebar/utils.js.map +1 -1
  18. package/build/components/header/index.js +5 -1
  19. package/build/components/header/index.js.map +1 -1
  20. package/build/components/provider/use-block-editor-settings.js +1 -1
  21. package/build/components/provider/use-block-editor-settings.js.map +1 -1
  22. package/build/dataviews/store/private-actions.js +1 -1
  23. package/build/dataviews/store/private-actions.js.map +1 -1
  24. package/build/store/actions.js +63 -2
  25. package/build/store/actions.js.map +1 -1
  26. package/build-module/components/collab-sidebar/add-comment.js +11 -5
  27. package/build-module/components/collab-sidebar/add-comment.js.map +1 -1
  28. package/build-module/components/collab-sidebar/comment-author-info.js +42 -14
  29. package/build-module/components/collab-sidebar/comment-author-info.js.map +1 -1
  30. package/build-module/components/collab-sidebar/comment-form.js +26 -22
  31. package/build-module/components/collab-sidebar/comment-form.js.map +1 -1
  32. package/build-module/components/collab-sidebar/comment-indicator-toolbar.js +8 -5
  33. package/build-module/components/collab-sidebar/comment-indicator-toolbar.js.map +1 -1
  34. package/build-module/components/collab-sidebar/comments.js +223 -172
  35. package/build-module/components/collab-sidebar/comments.js.map +1 -1
  36. package/build-module/components/collab-sidebar/hooks.js +89 -0
  37. package/build-module/components/collab-sidebar/hooks.js.map +1 -0
  38. package/build-module/components/collab-sidebar/index.js +82 -172
  39. package/build-module/components/collab-sidebar/index.js.map +1 -1
  40. package/build-module/components/collab-sidebar/utils.js +70 -24
  41. package/build-module/components/collab-sidebar/utils.js.map +1 -1
  42. package/build-module/components/header/index.js +5 -1
  43. package/build-module/components/header/index.js.map +1 -1
  44. package/build-module/components/provider/use-block-editor-settings.js +1 -1
  45. package/build-module/components/provider/use-block-editor-settings.js.map +1 -1
  46. package/build-module/dataviews/store/private-actions.js +2 -2
  47. package/build-module/dataviews/store/private-actions.js.map +1 -1
  48. package/build-module/store/actions.js +63 -2
  49. package/build-module/store/actions.js.map +1 -1
  50. package/build-style/style-rtl.css +60 -19
  51. package/build-style/style.css +60 -19
  52. package/build-types/components/collab-sidebar/add-comment.d.ts.map +1 -1
  53. package/build-types/components/collab-sidebar/comment-author-info.d.ts +3 -1
  54. package/build-types/components/collab-sidebar/comment-author-info.d.ts.map +1 -1
  55. package/build-types/components/collab-sidebar/comment-form.d.ts +3 -5
  56. package/build-types/components/collab-sidebar/comment-form.d.ts.map +1 -1
  57. package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts.map +1 -1
  58. package/build-types/components/collab-sidebar/comments.d.ts +1 -7
  59. package/build-types/components/collab-sidebar/comments.d.ts.map +1 -1
  60. package/build-types/components/collab-sidebar/hooks.d.ts +6 -0
  61. package/build-types/components/collab-sidebar/hooks.d.ts.map +1 -0
  62. package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
  63. package/build-types/components/collab-sidebar/utils.d.ts +11 -6
  64. package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
  65. package/build-types/components/header/index.d.ts.map +1 -1
  66. package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
  67. package/build-types/dataviews/store/private-actions.d.ts.map +1 -1
  68. package/build-types/store/actions.d.ts.map +1 -1
  69. package/package.json +37 -37
  70. package/src/components/collab-sidebar/add-comment.js +9 -4
  71. package/src/components/collab-sidebar/comment-author-info.js +51 -28
  72. package/src/components/collab-sidebar/comment-form.js +25 -24
  73. package/src/components/collab-sidebar/comment-indicator-toolbar.js +16 -5
  74. package/src/components/collab-sidebar/comments.js +271 -210
  75. package/src/components/collab-sidebar/hooks.js +95 -0
  76. package/src/components/collab-sidebar/index.js +107 -218
  77. package/src/components/collab-sidebar/style.scss +34 -20
  78. package/src/components/collab-sidebar/utils.js +73 -30
  79. package/src/components/header/index.js +6 -3
  80. package/src/components/provider/use-block-editor-settings.js +1 -0
  81. package/src/dataviews/store/private-actions.ts +6 -0
  82. package/src/store/actions.js +93 -2
  83. package/tsconfig.tsbuildinfo +1 -1
@@ -6,18 +6,22 @@ import clsx from 'clsx';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import { useState, RawHTML, useEffect, useMemo } from '@wordpress/element';
9
+ import { useState, RawHTML, useRef } from '@wordpress/element';
10
10
  import {
11
+ __experimentalText as Text,
11
12
  __experimentalHStack as HStack,
12
13
  __experimentalVStack as VStack,
13
14
  __experimentalConfirmDialog as ConfirmDialog,
14
15
  Button,
15
- DropdownMenu,
16
+ FlexItem,
17
+ privateApis as componentsPrivateApis,
16
18
  } from '@wordpress/components';
19
+ import { useDebounce } from '@wordpress/compose';
17
20
 
18
21
  import { published, moreVertical } from '@wordpress/icons';
19
22
  import { __, _x, sprintf, _n } from '@wordpress/i18n';
20
23
  import { useSelect, useDispatch } from '@wordpress/data';
24
+ import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
21
25
  import {
22
26
  store as blockEditorStore,
23
27
  privateApis as blockEditorPrivateApis,
@@ -29,30 +33,10 @@ import {
29
33
  import { unlock } from '../../lock-unlock';
30
34
  import CommentAuthorInfo from './comment-author-info';
31
35
  import CommentForm from './comment-form';
36
+ import { getCommentExcerpt } from './utils';
32
37
 
33
38
  const { useBlockElement } = unlock( blockEditorPrivateApis );
34
-
35
- /**
36
- * Finds the first block that has the specified comment ID.
37
- *
38
- * @param {string} commentId - The comment ID to search for.
39
- * @param {Array} blockList - The list of blocks to search through.
40
- * @return {string|null} The client ID of the found block, or null if not found.
41
- */
42
- const findBlockByCommentId = ( commentId, blockList ) => {
43
- for ( const block of blockList ) {
44
- if ( block.attributes?.blockCommentId === commentId ) {
45
- return block.clientId;
46
- }
47
- if ( block.innerBlocks ) {
48
- const found = findBlockByCommentId( commentId, block.innerBlocks );
49
- if ( found ) {
50
- return found;
51
- }
52
- }
53
- }
54
- return null;
55
- };
39
+ const { Menu } = unlock( componentsPrivateApis );
56
40
 
57
41
  /**
58
42
  * Renders the Comments component.
@@ -62,9 +46,6 @@ const findBlockByCommentId = ( commentId, blockList ) => {
62
46
  * @param {Function} props.onEditComment - The function to handle comment editing.
63
47
  * @param {Function} props.onAddReply - The function to add a reply to a comment.
64
48
  * @param {Function} props.onCommentDelete - The function to delete a comment.
65
- * @param {Function} props.onCommentResolve - The function to mark a comment as resolved.
66
- * @param {Function} props.onCommentReopen - The function to reopen a resolved comment.
67
- * @param {boolean} props.showCommentBoard - Whether to show the comment board.
68
49
  * @param {Function} props.setShowCommentBoard - The function to set the comment board visibility.
69
50
  * @return {React.ReactNode} The rendered Comments component.
70
51
  */
@@ -73,81 +54,47 @@ export function Comments( {
73
54
  onEditComment,
74
55
  onAddReply,
75
56
  onCommentDelete,
76
- onCommentResolve,
77
- onCommentReopen,
78
- showCommentBoard,
79
57
  setShowCommentBoard,
80
58
  } ) {
81
- const { blockCommentId, blocks } = useSelect( ( select ) => {
82
- const { getBlockAttributes, getSelectedBlockClientId, getBlocks } =
59
+ const blockCommentId = useSelect( ( select ) => {
60
+ const { getBlockAttributes, getSelectedBlockClientId } =
83
61
  select( blockEditorStore );
84
- const _clientId = getSelectedBlockClientId();
85
- return {
86
- blockCommentId: _clientId
87
- ? getBlockAttributes( _clientId )?.blockCommentId
88
- : null,
89
- blocks: getBlocks(),
90
- };
62
+ const clientId = getSelectedBlockClientId();
63
+ return clientId
64
+ ? getBlockAttributes( clientId )?.metadata?.commentId
65
+ : null;
91
66
  }, [] );
67
+ const [ selectedThread = blockCommentId, setSelectedThread ] = useState();
92
68
 
93
- const { flashBlock } = useDispatch( blockEditorStore );
94
-
95
- const clearThreadFocus = () => {
96
- setFocusThread( null );
97
- setShowCommentBoard( false );
98
- };
99
-
100
- const [ focusThread, setFocusThread ] = useState(
101
- showCommentBoard && blockCommentId ? blockCommentId : null
102
- );
103
-
104
- useEffect( () => {
105
- // Highlight comment when block is selected.
106
- if ( blockCommentId && ! focusThread ) {
107
- setFocusThread( blockCommentId );
108
- }
109
- }, [ blockCommentId, focusThread, blocks, setFocusThread ] );
69
+ const hasThreads = Array.isArray( threads ) && threads.length > 0;
70
+ if ( ! hasThreads ) {
71
+ return (
72
+ <VStack
73
+ alignment="left"
74
+ className="editor-collab-sidebar-panel__thread"
75
+ justify="flex-start"
76
+ spacing="2"
77
+ >
78
+ {
79
+ // translators: message displayed when there are no comments available
80
+ __( 'No comments available' )
81
+ }
82
+ </VStack>
83
+ );
84
+ }
110
85
 
111
- return (
112
- <>
113
- {
114
- // If there are no comments, show a message indicating no comments are available.
115
- ( ! Array.isArray( threads ) || threads.length === 0 ) && (
116
- <VStack
117
- alignment="left"
118
- className="editor-collab-sidebar-panel__thread"
119
- justify="flex-start"
120
- spacing="3"
121
- >
122
- {
123
- // translators: message displayed when there are no comments available
124
- __( 'No comments available' )
125
- }
126
- </VStack>
127
- )
128
- }
129
- { Array.isArray( threads ) &&
130
- threads.length > 0 &&
131
- threads.map( ( thread ) => (
132
- <Thread
133
- key={ thread.id }
134
- thread={ thread }
135
- onAddReply={ onAddReply }
136
- onCommentDelete={ onCommentDelete }
137
- onCommentResolve={ onCommentResolve }
138
- onCommentReopen={ onCommentReopen }
139
- onEditComment={ onEditComment }
140
- isFocused={ focusThread === thread.id }
141
- clearThreadFocus={ clearThreadFocus }
142
- setFocusThread={ setFocusThread }
143
- blockCommentId={ blockCommentId }
144
- blocks={ blocks }
145
- flashBlock={ flashBlock }
146
- setShowCommentBoard={ setShowCommentBoard }
147
- />
148
- ) ) }
149
- </>
150
- );
86
+ return threads.map( ( thread ) => (
87
+ <Thread
88
+ key={ thread.id }
89
+ thread={ thread }
90
+ onAddReply={ onAddReply }
91
+ onCommentDelete={ onCommentDelete }
92
+ onEditComment={ onEditComment }
93
+ isSelected={ selectedThread === thread.id }
94
+ setSelectedThread={ setSelectedThread }
95
+ setShowCommentBoard={ setShowCommentBoard }
96
+ />
97
+ ) );
151
98
  }
152
99
 
153
100
  function Thread( {
@@ -155,98 +102,181 @@ function Thread( {
155
102
  onEditComment,
156
103
  onAddReply,
157
104
  onCommentDelete,
158
- onCommentResolve,
159
- onCommentReopen,
160
- isFocused,
161
- clearThreadFocus,
162
- setFocusThread,
163
- blocks,
164
- flashBlock,
105
+ isSelected,
106
+ setSelectedThread,
165
107
  setShowCommentBoard,
166
108
  } ) {
167
- // Find first block that has this comment ID - run at component root level.
168
- const relatedBlock = useMemo( () => {
169
- if ( ! thread.id || ! blocks ) {
170
- return null;
171
- }
172
- return findBlockByCommentId( thread.id, blocks );
173
- }, [ thread.id, blocks ] );
109
+ const threadRef = useRef( null );
110
+ const { toggleBlockHighlight } = useDispatch( blockEditorStore );
111
+ const relatedBlockElement = useBlockElement( thread.blockClientId );
112
+ const debouncedToggleBlockHighlight = useDebounce(
113
+ toggleBlockHighlight,
114
+ 50
115
+ );
116
+
117
+ const onMouseEnter = () => {
118
+ debouncedToggleBlockHighlight( thread.blockClientId, true );
119
+ };
174
120
 
175
- const relatedBlockElement = useBlockElement( relatedBlock );
121
+ const onMouseLeave = () => {
122
+ debouncedToggleBlockHighlight( thread.blockClientId, false );
123
+ };
176
124
 
177
- const handleCommentSelect = ( threadId ) => {
125
+ const handleCommentSelect = ( { id, blockClientId } ) => {
178
126
  setShowCommentBoard( false );
179
- setFocusThread( threadId );
180
- if ( relatedBlock && relatedBlockElement ) {
127
+ setSelectedThread( id );
128
+ if ( blockClientId && relatedBlockElement ) {
181
129
  relatedBlockElement.scrollIntoView( {
182
130
  behavior: 'instant',
183
131
  block: 'center',
184
132
  } );
185
- flashBlock( relatedBlock );
186
133
  }
187
134
  };
188
135
 
136
+ const focusThread = () => {
137
+ threadRef.current?.focus();
138
+ };
139
+
140
+ const unselectThread = () => {
141
+ setSelectedThread( null );
142
+ setShowCommentBoard( false );
143
+ };
144
+
145
+ const replies = thread?.reply;
146
+ const lastReply = !! replies.length
147
+ ? replies[ replies.length - 1 ]
148
+ : undefined;
149
+ const restReplies = !! replies.length ? replies.slice( 0, -1 ) : [];
150
+
151
+ const commentExcerpt = getCommentExcerpt(
152
+ stripHTML( thread.content.rendered ),
153
+ 10
154
+ );
155
+ const ariaLabel = relatedBlockElement
156
+ ? sprintf(
157
+ // translators: %s: comment excerpt
158
+ __( 'Comment: %s' ),
159
+ commentExcerpt
160
+ )
161
+ : sprintf(
162
+ // translators: %s: comment excerpt
163
+ __( 'Original block deleted. Comment: %s' ),
164
+ commentExcerpt
165
+ );
166
+
189
167
  return (
168
+ // Disable reason: role="listitem" does in fact support aria-expanded.
169
+ // eslint-disable-next-line jsx-a11y/role-supports-aria-props
190
170
  <VStack
191
171
  className={ clsx( 'editor-collab-sidebar-panel__thread', {
192
- 'editor-collab-sidebar-panel__focus-thread': isFocused,
172
+ 'is-selected': isSelected,
193
173
  } ) }
194
- id={ thread.id }
195
- spacing="3"
196
- onClick={ () => handleCommentSelect( thread.id ) }
174
+ id={ `thread-${ thread.id }` }
175
+ spacing="2"
176
+ onClick={ () => handleCommentSelect( thread ) }
177
+ onMouseEnter={ onMouseEnter }
178
+ onMouseLeave={ onMouseLeave }
179
+ onFocus={ onMouseEnter }
180
+ onBlur={ onMouseLeave }
181
+ onKeyDown={ ( event ) => {
182
+ // Expand or Collapse thread.
183
+ if (
184
+ event.key === 'Enter' &&
185
+ event.currentTarget === event.target
186
+ ) {
187
+ if ( isSelected ) {
188
+ unselectThread();
189
+ } else {
190
+ handleCommentSelect( thread );
191
+ }
192
+ }
193
+ // Collapse thread and focus the thread.
194
+ if ( event.key === 'Escape' ) {
195
+ unselectThread();
196
+ focusThread();
197
+ }
198
+ } }
199
+ tabIndex={ 0 }
200
+ role="listitem"
201
+ ref={ threadRef }
202
+ aria-label={ ariaLabel }
203
+ aria-expanded={ isSelected }
197
204
  >
205
+ { ! relatedBlockElement && (
206
+ <Text as="p" weight={ 500 } variant="muted">
207
+ { __( 'Original block deleted.' ) }
208
+ </Text>
209
+ ) }
198
210
  <CommentBoard
199
211
  thread={ thread }
200
- onResolve={ onCommentResolve }
201
- onReopen={ onCommentReopen }
202
- onEdit={ onEditComment }
212
+ onEdit={ ( params = {} ) => {
213
+ const { status } = params;
214
+ onEditComment( params );
215
+ if ( status === 'approved' ) {
216
+ unselectThread();
217
+ focusThread();
218
+ }
219
+ } }
203
220
  onDelete={ onCommentDelete }
204
221
  status={ thread.status }
205
222
  />
206
- { 0 < thread?.reply?.length && (
207
- <>
208
- { ! isFocused && (
209
- <Button
210
- __next40pxDefaultSize
211
- variant="link"
212
- className="editor-collab-sidebar-panel__show-more-reply"
213
- onClick={ () => setFocusThread( thread.id ) }
214
- >
215
- { sprintf(
216
- // translators: %s: number of replies.
217
- _n(
218
- '%s more reply',
219
- '%s more replies',
220
- thread?.reply?.length
221
- ),
222
- thread?.reply?.length
223
- ) }
224
- </Button>
225
- ) }
226
-
227
- { isFocused &&
228
- thread.reply.map( ( reply ) => (
229
- <VStack
230
- key={ reply.id }
231
- className="editor-collab-sidebar-panel__child-thread"
232
- id={ reply.id }
233
- spacing="2"
234
- >
235
- { 'approved' !== thread.status && (
236
- <CommentBoard
237
- thread={ reply }
238
- onEdit={ onEditComment }
239
- onDelete={ onCommentDelete }
240
- />
241
- ) }
242
- { 'approved' === thread.status && (
243
- <CommentBoard thread={ reply } />
244
- ) }
245
- </VStack>
246
- ) ) }
247
- </>
223
+ { isSelected &&
224
+ replies.map( ( reply ) => (
225
+ <VStack
226
+ key={ reply.id }
227
+ className="editor-collab-sidebar-panel__child-thread"
228
+ id={ reply.id }
229
+ spacing="2"
230
+ >
231
+ <CommentBoard
232
+ thread={ reply }
233
+ onEdit={
234
+ 'approved' !== thread.status
235
+ ? onEditComment
236
+ : undefined
237
+ }
238
+ onDelete={
239
+ 'approved' !== thread.status
240
+ ? onCommentDelete
241
+ : undefined
242
+ }
243
+ />
244
+ </VStack>
245
+ ) ) }
246
+ { ! isSelected && restReplies.length > 0 && (
247
+ <HStack className="editor-collab-sidebar-panel__more-reply-separator">
248
+ <Button
249
+ size="compact"
250
+ variant="tertiary"
251
+ className="editor-collab-sidebar-panel__more-reply-button"
252
+ onClick={ () => setSelectedThread( thread.id ) }
253
+ >
254
+ { sprintf(
255
+ // translators: %s: number of replies.
256
+ _n(
257
+ '%s more reply',
258
+ '%s more replies',
259
+ restReplies.length
260
+ ),
261
+ restReplies.length
262
+ ) }
263
+ </Button>
264
+ </HStack>
248
265
  ) }
249
- { isFocused && (
266
+ { ! isSelected && lastReply && (
267
+ <CommentBoard
268
+ thread={ lastReply }
269
+ onEdit={
270
+ 'approved' !== thread.status ? onEditComment : undefined
271
+ }
272
+ onDelete={
273
+ 'approved' !== thread.status
274
+ ? onCommentDelete
275
+ : undefined
276
+ }
277
+ />
278
+ ) }
279
+ { isSelected && (
250
280
  <VStack
251
281
  className="editor-collab-sidebar-panel__child-thread"
252
282
  spacing="2"
@@ -254,36 +284,37 @@ function Thread( {
254
284
  <HStack alignment="left" spacing="3" justify="flex-start">
255
285
  <CommentAuthorInfo />
256
286
  </HStack>
257
- <VStack
258
- spacing="3"
259
- className="editor-collab-sidebar-panel__comment-field"
260
- >
287
+ <VStack spacing="2">
261
288
  <CommentForm
262
289
  onSubmit={ ( inputComment ) => {
263
290
  if ( 'approved' === thread.status ) {
264
- onCommentReopen( thread.id );
291
+ onEditComment( {
292
+ id: thread.id,
293
+ status: 'hold',
294
+ } );
265
295
  }
266
- onAddReply( inputComment, thread.id );
296
+ onAddReply( {
297
+ content: inputComment,
298
+ parent: thread.id,
299
+ } );
267
300
  } }
268
301
  onCancel={ ( event ) => {
302
+ threadRef.current?.focus();
269
303
  event.stopPropagation(); // Prevent the parent onClick from being triggered
270
- clearThreadFocus();
304
+ unselectThread();
271
305
  } }
272
- placeholderText={
273
- 'approved' === thread.status &&
274
- __(
275
- 'Adding a comment will re-open this discussion….'
276
- )
277
- }
278
306
  submitButtonText={
279
307
  'approved' === thread.status
280
- ? _x(
281
- 'Reopen & Reply',
282
- 'Reopen comment and add reply'
283
- )
284
- : _x( 'Reply', 'Add reply comment' )
308
+ ? __( 'Reopen & Reply' )
309
+ : __( 'Reply' )
285
310
  }
286
311
  rows={ 'approved' === thread.status ? 2 : 4 }
312
+ labelText={ sprintf(
313
+ // translators: %1$s: comment identifier, %2$s: author name
314
+ __( 'Reply to Comment %1$s by %2$s' ),
315
+ thread.id,
316
+ thread?.author_name || 'Unknown'
317
+ ) }
287
318
  />
288
319
  </VStack>
289
320
  </VStack>
@@ -292,19 +323,12 @@ function Thread( {
292
323
  );
293
324
  }
294
325
 
295
- const CommentBoard = ( {
296
- thread,
297
- onResolve,
298
- onReopen,
299
- onEdit,
300
- onDelete,
301
- status,
302
- } ) => {
326
+ const CommentBoard = ( { thread, onEdit, onDelete, status } ) => {
303
327
  const [ actionState, setActionState ] = useState( false );
304
328
  const [ showConfirmDialog, setShowConfirmDialog ] = useState( false );
305
329
 
306
330
  const handleConfirmDelete = () => {
307
- onDelete( thread.id );
331
+ onDelete( thread );
308
332
  setActionState( false );
309
333
  setShowConfirmDialog( false );
310
334
  };
@@ -317,27 +341,31 @@ const CommentBoard = ( {
317
341
  const actions = [
318
342
  onEdit &&
319
343
  status !== 'approved' && {
344
+ id: 'edit',
320
345
  title: _x( 'Edit', 'Edit comment' ),
321
346
  onClick: () => {
322
347
  setActionState( 'edit' );
323
348
  },
324
349
  },
325
350
  onDelete && {
351
+ id: 'delete',
326
352
  title: _x( 'Delete', 'Delete comment' ),
327
353
  onClick: () => {
328
354
  setActionState( 'delete' );
329
355
  setShowConfirmDialog( true );
330
356
  },
331
357
  },
332
- onReopen &&
358
+ onEdit &&
333
359
  status === 'approved' && {
360
+ id: 'reopen',
334
361
  title: _x( 'Reopen', 'Reopen comment' ),
335
362
  onClick: () => {
336
- onReopen( thread.id );
363
+ onEdit( { id: thread.id, status: 'hold' } );
337
364
  },
338
365
  },
339
366
  ];
340
367
 
368
+ const canResolve = thread?.parent === 0;
341
369
  const moreActions = actions.filter( ( item ) => item?.onClick );
342
370
 
343
371
  return (
@@ -347,10 +375,17 @@ const CommentBoard = ( {
347
375
  avatar={ thread?.author_avatar_urls?.[ 48 ] }
348
376
  name={ thread?.author_name }
349
377
  date={ thread?.date }
378
+ userId={ thread?.author }
350
379
  />
351
- <span className="editor-collab-sidebar-panel__comment-status">
352
- <HStack alignment="right" justify="flex-end" spacing="0">
353
- { 0 === thread?.parent && onResolve && (
380
+ <FlexItem
381
+ className="editor-collab-sidebar-panel__comment-status"
382
+ onClick={ ( event ) => {
383
+ // Prevent the thread from being selected.
384
+ event.stopPropagation();
385
+ } }
386
+ >
387
+ <HStack spacing="0">
388
+ { canResolve && (
354
389
  <Button
355
390
  label={ _x(
356
391
  'Resolve',
@@ -361,33 +396,59 @@ const CommentBoard = ( {
361
396
  disabled={ status === 'approved' }
362
397
  accessibleWhenDisabled={ status === 'approved' }
363
398
  onClick={ () => {
364
- onResolve( thread.id );
399
+ onEdit( {
400
+ id: thread.id,
401
+ status: 'approved',
402
+ } );
365
403
  } }
366
404
  />
367
405
  ) }
368
- { 0 < moreActions.length && (
369
- <DropdownMenu
370
- icon={ moreVertical }
371
- label={ _x(
372
- 'Select an action',
373
- 'Select comment action'
374
- ) }
375
- className="editor-collab-sidebar-panel__comment-dropdown-menu"
376
- controls={ moreActions }
406
+ <Menu placement="bottom-end">
407
+ <Menu.TriggerButton
408
+ render={
409
+ <Button
410
+ size="small"
411
+ icon={ moreVertical }
412
+ label={ __( 'Actions' ) }
413
+ disabled={ ! moreActions.length }
414
+ accessibleWhenDisabled
415
+ />
416
+ }
377
417
  />
378
- ) }
418
+ <Menu.Popover>
419
+ { moreActions.map( ( action ) => (
420
+ <Menu.Item
421
+ key={ action.id }
422
+ onClick={ () => action.onClick() }
423
+ >
424
+ <Menu.ItemLabel>
425
+ { action.title }
426
+ </Menu.ItemLabel>
427
+ </Menu.Item>
428
+ ) ) }
429
+ </Menu.Popover>
430
+ </Menu>
379
431
  </HStack>
380
- </span>
432
+ </FlexItem>
381
433
  </HStack>
382
434
  { 'edit' === actionState ? (
383
435
  <CommentForm
384
436
  onSubmit={ ( value ) => {
385
- onEdit( thread.id, value );
437
+ onEdit( {
438
+ id: thread.id,
439
+ content: value,
440
+ } );
386
441
  setActionState( false );
387
442
  } }
388
443
  onCancel={ () => handleCancel() }
389
444
  thread={ thread }
390
445
  submitButtonText={ _x( 'Update', 'verb' ) }
446
+ labelText={ sprintf(
447
+ // translators: %1$s: comment identifier, %2$s: author name.
448
+ __( 'Edit Comment %1$s by %2$s' ),
449
+ thread.id,
450
+ thread?.author_name || 'Unknown'
451
+ ) }
391
452
  />
392
453
  ) : (
393
454
  <RawHTML className="editor-collab-sidebar-panel__user-comment">