1
1
import { useCallback , useMemo } from 'react'
2
2
import { useApolloClient } from '@apollo/client'
3
3
import styles from './comment.module.css'
4
+ import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
5
+ import { COMMENT_WITH_NEW_RECURSIVE } from '../fragments/comments'
6
+ import { commentsViewedAfterComment } from '../lib/new-comments'
4
7
import {
5
8
itemUpdateQuery ,
6
9
commentUpdateFragment ,
7
- prepareComments ,
8
- dedupeNewComments ,
9
- collectAllNewComments ,
10
- showAllNewCommentsRecursively
10
+ getLatestCommentCreatedAt ,
11
+ updateAncestorsCommentCount
11
12
} from '../lib/comments'
12
13
14
+ // filters out new comments, by id, that already exist in the item's comments
15
+ // preventing duplicate comments from being injected
16
+ function dedupeNewComments ( newComments , comments ) {
17
+ console . log ( 'dedupeNewComments' , newComments , comments )
18
+ const existingIds = new Set ( comments . map ( c => c . id ) )
19
+ return newComments . filter ( id => ! existingIds . has ( id ) )
20
+ }
21
+
22
+ // prepares and creates a new comments fragment for injection into the cache
23
+ // returns a function that can be used to update an item's comments field
24
+ function prepareComments ( client , newComments ) {
25
+ return ( data ) => {
26
+ // newComments is an array of comment ids that allows usto read the latest newComments from the cache,
27
+ // guaranteeing that we're not reading stale data
28
+ const freshNewComments = newComments . map ( id => {
29
+ const fragment = client . cache . readFragment ( {
30
+ id : `Item:${ id } ` ,
31
+ fragment : COMMENT_WITH_NEW_RECURSIVE ,
32
+ fragmentName : 'CommentWithNewRecursive'
33
+ } )
34
+
35
+ if ( ! fragment ) {
36
+ return null
37
+ }
38
+
39
+ // marking it as injected so that the new comment can be outlined
40
+ return { ...fragment , injected : true }
41
+ } ) . filter ( Boolean )
42
+
43
+ // count the total number of new comments including its nested new comments
44
+ let totalNComments = freshNewComments . length
45
+ for ( const comment of freshNewComments ) {
46
+ totalNComments += ( comment . ncomments || 0 )
47
+ }
48
+
49
+ // update all ancestors, but not the item itself
50
+ const ancestors = data . path . split ( '.' ) . slice ( 0 , - 1 )
51
+ updateAncestorsCommentCount ( client . cache , ancestors , totalNComments )
52
+
53
+ // update commentsViewedAt with the most recent fresh new comment
54
+ // quirk: this is not the most recent comment, it's the most recent comment in the freshNewComments array
55
+ // as such, the next visit will not outline other new comments that have not been injected yet
56
+ const latestCommentCreatedAt = getLatestCommentCreatedAt ( freshNewComments , data . createdAt )
57
+ const rootId = data . path . split ( '.' ) [ 0 ]
58
+ commentsViewedAfterComment ( rootId , latestCommentCreatedAt )
59
+
60
+ // return the updated item with the new comments injected
61
+ return {
62
+ ...data ,
63
+ comments : { ...data . comments , comments : [ ...freshNewComments , ...data . comments . comments ] } ,
64
+ ncomments : data . ncomments + totalNComments ,
65
+ newComments : [ ]
66
+ }
67
+ }
68
+ }
69
+
70
+ // recursively processes and displays all new comments for a thread
71
+ // handles comment injection at each level, respecting depth limits
72
+ function showAllNewCommentsRecursively ( client , item , currentDepth = 1 ) {
73
+ // handle new comments at this item level
74
+ if ( item . newComments && item . newComments . length > 0 ) {
75
+ const dedupedNewComments = dedupeNewComments ( item . newComments , item . comments ?. comments )
76
+
77
+ if ( dedupedNewComments . length > 0 ) {
78
+ const payload = prepareComments ( client , dedupedNewComments )
79
+ commentUpdateFragment ( client , item . id , payload )
80
+ }
81
+ }
82
+
83
+ // read the updated item from the cache
84
+ // this is necessary because the item may have been updated by the time we get to the child comments
85
+ const updatedItem = client . cache . readFragment ( {
86
+ id : `Item:${ item . id } ` ,
87
+ fragment : COMMENT_WITH_NEW_RECURSIVE ,
88
+ fragmentName : 'CommentWithNewRecursive'
89
+ } )
90
+
91
+ // recursively handle new comments in child comments
92
+ if ( updatedItem . comments ?. comments && currentDepth < ( COMMENT_DEPTH_LIMIT - 1 ) ) {
93
+ for ( const childComment of updatedItem . comments . comments ) {
94
+ showAllNewCommentsRecursively ( client , childComment , currentDepth + 1 )
95
+ }
96
+ }
97
+ }
98
+
13
99
export const ShowNewComments = ( { topLevel, sort, comments, itemId, item, setHasNewComments, newComments = [ ] , depth = 1 } ) => {
14
100
const client = useApolloClient ( )
15
101
// if item is provided, we're showing all new comments for a thread,
16
102
// otherwise we're showing new comments for a comment
17
103
const isThread = ! topLevel && item ?. path . split ( '.' ) . length === 2
18
- const allNewComments = useMemo ( ( ) => {
19
- if ( isThread ) {
20
- // TODO: well are we only collecting all new comments just for a fancy UI?
21
- // TODO2: also, we're not deduping new comments here, so we're showing duplicates
22
- return collectAllNewComments ( item , depth )
23
- }
24
- return dedupeNewComments ( newComments , comments )
25
- } , [ isThread , item , newComments , comments , depth ] )
104
+ const allNewComments = useMemo ( ( ) => dedupeNewComments ( newComments , comments ) , [ newComments , comments ] )
26
105
27
106
const showNewComments = useCallback ( ( ) => {
28
- if ( isThread ) {
29
- showAllNewCommentsRecursively ( client , item , depth )
30
- } else {
31
- // fetch the latest version of the comments from the cache by their ids
107
+ if ( topLevel ) {
32
108
const payload = prepareComments ( client , allNewComments )
33
- if ( topLevel ) {
34
- itemUpdateQuery ( client , itemId , sort , payload )
35
- } else {
36
- commentUpdateFragment ( client , itemId , payload )
37
- }
109
+ itemUpdateQuery ( client , itemId , sort , payload )
110
+ } else {
111
+ showAllNewCommentsRecursively ( client , item , depth )
38
112
}
39
113
setHasNewComments ( false )
40
- } , [ client , itemId , allNewComments , topLevel , sort ] )
114
+ } , [ client , itemId , allNewComments , topLevel , sort , item ] )
41
115
42
116
if ( allNewComments . length === 0 ) {
43
117
return null
@@ -48,9 +122,7 @@ export const ShowNewComments = ({ topLevel, sort, comments, itemId, item, setHas
48
122
onClick = { showNewComments }
49
123
className = { `${ topLevel && `d-block fw-bold ${ styles . comment } pb-2` } d-flex align-items-center gap-2 px-3 pointer` }
50
124
>
51
- { allNewComments . length > 1
52
- ? `${ isThread ? 'show all ' : '' } ${ allNewComments . length } new comments`
53
- : 'show new comment' }
125
+ show { isThread ? 'all' : '' } new comments
54
126
< div className = { styles . newCommentDot } />
55
127
</ div >
56
128
)
0 commit comments