@@ -11,9 +11,11 @@ import styled, {css} from 'styled-components'
11
11
import {
12
12
applyCommentsFieldAttr ,
13
13
type CommentCreatePayload ,
14
+ type CommentMessage ,
14
15
type CommentsUIMode ,
15
16
isTextSelectionComment ,
16
17
useComments ,
18
+ useCommentsAuthoringPath ,
17
19
useCommentsEnabled ,
18
20
useCommentsScroll ,
19
21
useCommentsSelectedPath ,
@@ -22,6 +24,14 @@ import {
22
24
import { COMMENTS_HIGHLIGHT_HUE_KEY } from '../../src/constants'
23
25
import { CommentsFieldButton } from './CommentsFieldButton'
24
26
27
+ // When the form is temporarily set to `readOnly` while reconnecting, the form
28
+ // will be re-rendered and any comment that is being authored will be lost.
29
+ // To avoid this, we cache the comment message in a map and restore it when the
30
+ // field is re-rendered.
31
+ const messageCache = new Map < string , CommentMessage > ( )
32
+
33
+ const EMPTY_ARRAY : [ ] = [ ]
34
+
25
35
const HIGHLIGHT_BLOCK_VARIANTS : Variants = {
26
36
initial : {
27
37
opacity : 0 ,
@@ -74,8 +84,6 @@ function CommentFieldInner(
74
84
} ,
75
85
) {
76
86
const { mode} = props
77
- const [ open , setOpen ] = useState < boolean > ( false )
78
- const [ value , setValue ] = useState < PortableTextBlock [ ] | null > ( null )
79
87
80
88
const currentUser = useCurrentUser ( )
81
89
const { element : boundaryElement } = useBoundaryElement ( )
@@ -94,18 +102,28 @@ function CommentFieldInner(
94
102
} = useComments ( )
95
103
const { upsellData, handleOpenDialog} = useCommentsUpsell ( )
96
104
const { selectedPath, setSelectedPath} = useCommentsSelectedPath ( )
105
+ const { authoringPath, setAuthoringPath} = useCommentsAuthoringPath ( )
97
106
const { scrollToGroup} = useCommentsScroll ( {
98
107
boundaryElement,
99
108
} )
100
109
101
110
const fieldTitle = useMemo ( ( ) => getSchemaTypeTitle ( props . schemaType ) , [ props . schemaType ] )
111
+ const stringPath = useMemo ( ( ) => PathUtils . toString ( props . path ) , [ props . path ] )
112
+
113
+ // Use the cached value if it exists as the initial value
114
+ const cachedValue = messageCache . get ( stringPath ) || null
115
+
116
+ const [ value , setValue ] = useState < PortableTextBlock [ ] | null > ( cachedValue )
117
+
118
+ // If the path of the field matches the authoring path, the comment input should be open.
119
+ const isOpen = useMemo ( ( ) => authoringPath === stringPath , [ authoringPath , stringPath ] )
102
120
103
121
// Determine if the current field is selected
104
122
const isSelected = useMemo ( ( ) => {
105
123
if ( ! isCommentsOpen ) return false
106
124
if ( selectedPath ?. origin === 'form' || selectedPath ?. origin === 'url' ) return false
107
- return selectedPath ?. fieldPath === PathUtils . toString ( props . path )
108
- } , [ isCommentsOpen , props . path , selectedPath ?. fieldPath , selectedPath ?. origin ] )
125
+ return selectedPath ?. fieldPath === stringPath
126
+ } , [ isCommentsOpen , selectedPath ?. fieldPath , selectedPath ?. origin , stringPath ] )
109
127
110
128
const isInlineCommentThread = useMemo ( ( ) => {
111
129
return comments . data . open
@@ -115,17 +133,21 @@ function CommentFieldInner(
115
133
116
134
// Total number of comments for the current field
117
135
const count = useMemo ( ( ) => {
118
- const stringPath = PathUtils . toString ( props . path )
119
-
120
136
const commentsCount = comments . data . open
121
137
. map ( ( c ) => ( c . fieldPath === stringPath ? c . commentsCount : 0 ) )
122
138
. reduce ( ( acc , val ) => acc + val , 0 )
123
139
124
140
return commentsCount || 0
125
- } , [ comments . data . open , props . path ] )
141
+ } , [ comments . data . open , stringPath ] )
126
142
127
143
const hasComments = Boolean ( count > 0 )
128
144
145
+ const resetMessageValue = useCallback ( ( ) => {
146
+ // Reset the value and remove the message from the cache
147
+ setValue ( null )
148
+ messageCache . delete ( stringPath )
149
+ } , [ stringPath ] )
150
+
129
151
const handleClick = useCallback ( ( ) => {
130
152
// When clicking a comment button when the field has comments, we want to:
131
153
if ( hasComments ) {
@@ -134,8 +156,9 @@ function CommentFieldInner(
134
156
setStatus ( 'open' )
135
157
}
136
158
137
- // 2. Close the comment input if it's open
138
- setOpen ( false )
159
+ // 2. Ensure that the authoring path is reset when clicking
160
+ // the comment button when the field has comments.
161
+ setAuthoringPath ( null )
139
162
140
163
// 3. Open the comments inspector
141
164
onCommentsOpen ?.( )
@@ -171,19 +194,24 @@ function CommentFieldInner(
171
194
return
172
195
}
173
196
174
- // Else, toggle the comment input open/closed
175
- setOpen ( ( v ) => ! v )
197
+ // If the field is open (i.e. the authoring path is set to the current field)
198
+ // we close the field by resetting the authoring path. If the field is not open,
199
+ // we set the authoring path to the current field so that the comment form is opened.
200
+ setAuthoringPath ( isOpen ? null : stringPath )
176
201
} , [
177
202
comments . data . open ,
178
203
handleOpenDialog ,
179
204
hasComments ,
205
+ isOpen ,
180
206
mode ,
181
207
onCommentsOpen ,
182
208
props . path ,
183
209
scrollToGroup ,
210
+ setAuthoringPath ,
184
211
setSelectedPath ,
185
212
setStatus ,
186
213
status ,
214
+ stringPath ,
187
215
upsellData ,
188
216
] )
189
217
@@ -200,7 +228,7 @@ function CommentFieldInner(
200
228
status : 'open' ,
201
229
threadId : newThreadId ,
202
230
// New comments have no reactions
203
- reactions : [ ] ,
231
+ reactions : EMPTY_ARRAY ,
204
232
}
205
233
206
234
// Execute the create mutation
@@ -216,8 +244,7 @@ function CommentFieldInner(
216
244
setStatus ( 'open' )
217
245
}
218
246
219
- // Reset the value
220
- setValue ( null )
247
+ resetMessageValue ( )
221
248
222
249
// Scroll to the thread
223
250
setSelectedPath ( {
@@ -232,14 +259,23 @@ function CommentFieldInner(
232
259
onCommentsOpen ,
233
260
operation ,
234
261
props . path ,
262
+ resetMessageValue ,
235
263
scrollToGroup ,
236
264
setSelectedPath ,
237
265
setStatus ,
238
266
status ,
239
267
value ,
240
268
] )
241
269
242
- const handleDiscard = useCallback ( ( ) => setValue ( null ) , [ ] )
270
+ const handleClose = useCallback ( ( ) => setAuthoringPath ( null ) , [ setAuthoringPath ] )
271
+
272
+ const handleOnChange = useCallback (
273
+ ( nextValue : CommentMessage ) => {
274
+ setValue ( nextValue )
275
+ messageCache . set ( stringPath , nextValue )
276
+ } ,
277
+ [ stringPath ] ,
278
+ )
243
279
244
280
const internalComments : FieldProps [ '__internal_comments' ] = useMemo (
245
281
( ) => ( {
@@ -250,29 +286,31 @@ function CommentFieldInner(
250
286
fieldTitle = { fieldTitle }
251
287
isCreatingDataset = { isCreatingDataset }
252
288
mentionOptions = { mentionOptions }
253
- onChange = { setValue }
289
+ onChange = { handleOnChange }
254
290
onClick = { handleClick }
291
+ onClose = { handleClose }
255
292
onCommentAdd = { handleCommentAdd }
256
- onDiscard = { handleDiscard }
257
- open = { open }
258
- setOpen = { setOpen }
293
+ onDiscard = { resetMessageValue }
294
+ open = { isOpen }
259
295
value = { value }
260
296
/>
261
297
) ,
262
298
hasComments,
263
- isAddingComment : open ,
299
+ isAddingComment : isOpen ,
264
300
} ) ,
265
301
[
266
302
currentUser ,
267
303
count ,
268
304
fieldTitle ,
305
+ isCreatingDataset ,
269
306
mentionOptions ,
307
+ handleOnChange ,
270
308
handleClick ,
309
+ handleClose ,
271
310
handleCommentAdd ,
272
- handleDiscard ,
273
- open ,
311
+ resetMessageValue ,
312
+ isOpen ,
274
313
value ,
275
- isCreatingDataset ,
276
314
hasComments ,
277
315
] ,
278
316
)
0 commit comments