Skip to content

Commit f0e3516

Browse files
authored
Refactor live comments and comment injection (#2462)
* Fix duplicate comment on pessimistic creation - comment creation checks for comment's ID existence in cache - invoice.confirmedAt included in useCanEdit deps for anons live comments * switch to some as sets are not worth it * only check for duplicates if a pessimistic payment method has been used * default to empty array * add comment about side-effects * record ownership of an item to avoid injecting it via live comments * trigger check only if the incoming comment is ours, cleanup * correct conditions, correct comments, light cleanup * fix: add defensive condition to ownership recorder, better name * refactor: unified comment injection logic with deduplication, useCommentsView hook; revert sessionStorage-based fix * adjust live comments naming around the codebase * listen for hmac presence for anon edits * always return the injected comment createdAt to bump live comments * refactor: improve live comments hook readability - latest comment createdAt persistence helper - preserveScroll returns the returning value of the callback - compact conditional logic - refresh code comments - refresh naming - group constants - reorder imports * flat comment injection, fetch flat comments instead of the entire subtree that would've been deduplicated anyway, cleanup * always align new comment fragment to the comments query structure * generic useCommentsView hook * update comment counts if live injecting into fragments without comments field * fix: pass parentId, if a comment has a top level parent it always has the comments field * fix: update CommentsViewAt only if we actually injected a comment into cache * correct injectComment result usage * pass markViewedAt to further centralize side effects, remove live from Item server typedefs * fix: don't update counts for ancestors that are already up to date, update commentsViewedAt per batch not per comment * port: fix coalesce, useCommentsView hook and outline changes * update hmac field in cache on paid invoice, hmac as useCanEdit effect dependency * comments and light cleanup, update useCommentsView * efficient hasComments logic for live comments, establish a gql fragment * fix: typo on topLevel evaluation * limit extra evaluations to live comments scenarios * update comments * support live comments ncomments increments for anon view tracking
1 parent fbeba23 commit f0e3516

File tree

15 files changed

+173
-292
lines changed

15 files changed

+173
-292
lines changed

api/resolvers/item.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ export default {
743743
subMaxBoost: subAgg?._max.boost || 0
744744
}
745745
},
746-
newComments: async (parent, { topLevelId, after }, { models, me }) => {
746+
newComments: async (parent, { itemId, after }, { models, me }) => {
747747
const comments = await itemQueryWithMeta({
748748
me,
749749
models,
@@ -757,7 +757,7 @@ export default {
757757
'"Item"."created_at" > $2'
758758
)}
759759
ORDER BY "Item"."created_at" ASC`
760-
}, Number(topLevelId), after)
760+
}, Number(itemId), after)
761761

762762
return { comments }
763763
}

api/typeDefs/item.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default gql`
1212
auctionPosition(sub: String, id: ID, boost: Int): Int!
1313
boostPosition(sub: String, id: ID, boost: Int): BoostPositions!
1414
itemRepetition(parentId: ID): Int!
15-
newComments(topLevelId: ID, after: Date): Comments!
15+
newComments(itemId: ID, after: Date): Comments!
1616
}
1717
1818
type BoostPositions {
@@ -151,7 +151,6 @@ export default gql`
151151
ncomments: Int!
152152
nDirectComments: Int!
153153
comments(sort: String, cursor: String): Comments!
154-
injected: Boolean!
155154
path: String
156155
position: Int
157156
prior: Int

components/comment.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,11 @@ export default function Comment ({
120120

121121
const classes = ref.current.classList
122122
const hasOutline = classes.contains('outline-new-comment')
123-
const hasInjectedOutline = classes.contains('outline-new-injected-comment')
123+
const hasLiveOutline = classes.contains('outline-new-live-comment')
124124
const hasOutlineUnset = classes.contains('outline-new-comment-unset')
125125

126126
// don't try to untrack and unset the outline if the comment is not outlined or we already unset the outline
127-
if (!(hasInjectedOutline || hasOutline) || hasOutlineUnset) return
127+
if (!(hasLiveOutline || hasOutline) || hasOutlineUnset) return
128128

129129
classes.add('outline-new-comment-unset')
130130
// untrack new comment and its descendants if it's not a live comment
@@ -166,22 +166,22 @@ export default function Comment ({
166166
const viewedAt = me?.id ? meViewedAt : router.query.commentsViewedAt
167167

168168
const isNewComment = viewedAt && itemCreatedAt > viewedAt
169-
// injected comments are new regardless of me or anon view time
169+
// live comments are new regardless of me or anon view time
170170
const rootLast = new Date(root.lastCommentAt || root.createdAt).getTime()
171-
const isNewInjectedComment = item.injected && itemCreatedAt > (meViewedAt || rootLast)
171+
const isNewLiveComment = item.live && itemCreatedAt > (meViewedAt || rootLast)
172172

173-
if (!isNewComment && !isNewInjectedComment) return
173+
if (!isNewComment && !isNewLiveComment) return
174174

175-
if (item.injected) {
176-
// newly injected comments (item.injected) have to use a different class to outline every new comment
177-
ref.current.classList.add('outline-new-injected-comment')
175+
if (item.live) {
176+
// live comments (item.live) have to use a different class to outline every new comment
177+
ref.current.classList.add('outline-new-live-comment')
178178

179179
// wait for the injection animation to end before removing its class
180180
ref.current.addEventListener('animationend', () => {
181-
ref.current.classList.remove(styles.injectedComment)
181+
ref.current.classList.remove(styles.liveComment)
182182
}, { once: true })
183183
// animate the live comment injection
184-
ref.current.classList.add(styles.injectedComment)
184+
ref.current.classList.add(styles.liveComment)
185185
} else {
186186
ref.current.classList.add('outline-new-comment')
187187
}

components/comment.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139
padding-top: .5rem;
140140
}
141141

142-
.injectedComment {
142+
.liveComment {
143143
animation: fadeIn 0.5s ease-out;
144144
}
145145

components/comments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default function Comments ({
7171
const router = useRouter()
7272

7373
// fetch new comments that arrived after the lastCommentAt, and update the item.comments field in cache
74-
useLiveComments(parentId, lastCommentAt || parentCreatedAt, router.query.sort)
74+
useLiveComments(parentId, lastCommentAt || parentCreatedAt)
7575

7676
// new comments navigator, tracks new comments and provides navigation controls
7777
const { navigator } = useCommentsNavigatorContext()

components/preserve-scroll.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ export default function preserveScroll (callback) {
44

55
// if the scroll position is at the top, we don't need to preserve it, just call the callback
66
if (scrollTop <= 0) {
7-
callback()
8-
return
7+
return callback()
98
}
109

1110
// get a reference element at the center of the viewport to track if content is added above it
@@ -49,5 +48,5 @@ export default function preserveScroll (callback) {
4948

5049
observer.observe(document.body, { childList: true, subtree: true })
5150

52-
callback()
51+
return callback()
5352
}

components/reply.js

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Form, MarkdownInput } from '@/components/form'
22
import styles from './reply.module.css'
3-
import { COMMENTS } from '@/fragments/comments'
43
import { useMe } from './me'
54
import { forwardRef, useCallback, useEffect, useState, useRef, useMemo } from 'react'
65
import { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
@@ -10,9 +9,9 @@ import { useShowModal } from './modal'
109
import { Button } from 'react-bootstrap'
1110
import { useRoot } from './root'
1211
import { CREATE_COMMENT } from '@/fragments/paidAction'
12+
import { injectComment } from '@/lib/comments'
1313
import useItemSubmit from './use-item-submit'
1414
import gql from 'graphql-tag'
15-
import { updateAncestorsCommentCount } from '@/lib/comments'
1615
import useCommentsView from './use-comments-view'
1716

1817
export default forwardRef(function Reply ({
@@ -52,23 +51,11 @@ export default forwardRef(function Reply ({
5251
update (cache, { data: { upsertComment: { result, invoice } } }) {
5352
if (!result) return
5453

55-
cache.modify({
56-
id: `Item:${parentId}`,
57-
fields: {
58-
comments (existingComments = {}) {
59-
const newCommentRef = cache.writeFragment({
60-
data: result,
61-
fragment: COMMENTS,
62-
fragmentName: 'CommentsRecursive'
63-
})
64-
return {
65-
cursor: existingComments.cursor,
66-
comments: [newCommentRef, ...(existingComments?.comments || [])]
67-
}
68-
}
69-
},
70-
optimistic: true
71-
})
54+
// inject the new comment into the cache
55+
const injected = injectComment(cache, result)
56+
if (injected) {
57+
markCommentViewedAt(result.createdAt, { ncomments: 1 })
58+
}
7259

7360
// no lag for itemRepetition
7461
if (!item.mine && me) {
@@ -80,15 +67,6 @@ export default forwardRef(function Reply ({
8067
}
8168
})
8269
}
83-
84-
const ancestors = item.path.split('.')
85-
86-
// update all ancestors
87-
updateAncestorsCommentCount(cache, ancestors, 1, parentId)
88-
89-
// so that we don't see indicator for our own comments, we record this comments as the latest time
90-
// but we also have record num comments, in case someone else commented when we did
91-
markCommentViewedAt(result.createdAt, { ncomments: 1 })
9270
}
9371
},
9472
onSuccessfulSubmit: (data, { resetForm }) => {

components/use-can-edit.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export default function useCanEdit (item) {
2020
const anonEdit = !!invParams && !me && Number(item.user.id) === USER_ID.anon
2121
// anonEdit should not override canEdit, but only allow edits if they aren't already allowed
2222
setCanEdit(canEdit => canEdit || anonEdit)
23-
}, [])
23+
// update when the hmac gets set
24+
}, [item?.invoice?.hmac])
2425

2526
return [canEdit, setCanEdit, editThreshold]
2627
}

components/use-comments-navigator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function useCommentsNavigator () {
109109
node.classList.remove(
110110
'outline-it',
111111
'outline-new-comment',
112-
'outline-new-injected-comment'
112+
'outline-new-live-comment'
113113
)
114114
node.classList.add('outline-new-comment-unset')
115115
}

0 commit comments

Comments
 (0)