Skip to content

Commit f8ec5f4

Browse files
committed
enhance: auto-show new comments when tab is not focused; cleanup: move ShowNewComments functions to dedicated comments.js
1 parent c613be4 commit f8ec5f4

File tree

2 files changed

+99
-82
lines changed

2 files changed

+99
-82
lines changed

components/show-new-comments.js

Lines changed: 13 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,8 @@
1-
import { useCallback, useMemo } from 'react'
1+
import { useCallback, useMemo, useEffect } from 'react'
22
import { useApolloClient } from '@apollo/client'
3-
import { COMMENT_WITH_NEW_RECURSIVE } from '../fragments/comments'
43
import styles from './comment.module.css'
5-
import { itemUpdateQuery, commentUpdateFragment, getLatestCommentCreatedAt } from './use-live-comments'
6-
import { updateAncestorsCommentCount } from '@/lib/comments'
7-
import { COMMENT_DEPTH_LIMIT } from '@/lib/constants'
8-
import { commentsViewedAfterComment } from '@/lib/new-comments'
9-
10-
function prepareComments (client, newComments) {
11-
return (data) => {
12-
// newComments is an array of comment ids that allows us
13-
// to read the latest newComments from the cache, guaranteeing that we're not reading stale data
14-
const freshNewComments = newComments.map(id => {
15-
const fragment = client.cache.readFragment({
16-
id: `Item:${id}`,
17-
fragment: COMMENT_WITH_NEW_RECURSIVE,
18-
fragmentName: 'CommentWithNewRecursive'
19-
})
20-
21-
if (!fragment) {
22-
return null
23-
}
24-
25-
return fragment
26-
}).filter(Boolean)
27-
28-
// count the total number of new comments including its nested new comments
29-
let totalNComments = freshNewComments.length
30-
for (const comment of freshNewComments) {
31-
totalNComments += (comment.ncomments || 0)
32-
}
33-
34-
// update all ancestors, but not the item itself
35-
const ancestors = data.path.split('.').slice(0, -1)
36-
updateAncestorsCommentCount(client.cache, ancestors, totalNComments)
37-
38-
// update commentsViewedAt with the most recent comment
39-
const latestCommentCreatedAt = getLatestCommentCreatedAt(freshNewComments, data.createdAt)
40-
const rootId = data.path.split('.')[0]
41-
commentsViewedAfterComment(rootId, latestCommentCreatedAt)
42-
43-
return {
44-
...data,
45-
comments: { ...data.comments, comments: [...freshNewComments, ...data.comments.comments] },
46-
ncomments: data.ncomments + totalNComments,
47-
newComments: []
48-
}
49-
}
50-
}
51-
52-
function showAllNewCommentsRecursively (client, item, currentDepth = 1) {
53-
// handle new comments at this item level
54-
if (item.newComments && item.newComments.length > 0) {
55-
const dedupedNewComments = dedupeNewComments(item.newComments, item.comments?.comments)
56-
57-
if (dedupedNewComments.length > 0) {
58-
const payload = prepareComments(client, dedupedNewComments)
59-
commentUpdateFragment(client, item.id, payload)
60-
}
61-
}
62-
63-
// recursively handle new comments in child comments
64-
if (item.comments?.comments && currentDepth < (COMMENT_DEPTH_LIMIT - 1)) {
65-
for (const childComment of item.comments.comments) {
66-
showAllNewCommentsRecursively(client, childComment, currentDepth + 1)
67-
}
68-
}
69-
}
70-
71-
function dedupeNewComments (newComments, comments) {
72-
const existingIds = new Set(comments.map(c => c.id))
73-
return newComments.filter(id => !existingIds.has(id))
74-
}
75-
76-
function collectAllNewComments (item, currentDepth = 1) {
77-
const allNewComments = [...(item.newComments || [])]
78-
if (item.comments?.comments && currentDepth < (COMMENT_DEPTH_LIMIT - 1)) {
79-
for (const comment of item.comments.comments) {
80-
allNewComments.push(...collectAllNewComments(comment, currentDepth + 1))
81-
}
82-
}
83-
return allNewComments
84-
}
4+
import { itemUpdateQuery, commentUpdateFragment } from './use-live-comments'
5+
import { prepareComments, dedupeNewComments, collectAllNewComments, showAllNewCommentsRecursively } from '@/lib/comments'
856

867
export function ShowNewComments ({ topLevel, sort, comments, itemId, item, setHasNewComments, newComments = [], depth = 1 }) {
878
const client = useApolloClient()
@@ -112,6 +33,16 @@ export function ShowNewComments ({ topLevel, sort, comments, itemId, item, setHa
11233
setHasNewComments(false)
11334
}, [client, itemId, allNewComments, topLevel, sort])
11435

36+
useEffect(() => {
37+
const handleVisibilityChange = () => {
38+
if (document.hidden && allNewComments.length > 0) {
39+
showNewComments()
40+
}
41+
}
42+
document.addEventListener('visibilitychange', handleVisibilityChange)
43+
return () => document.removeEventListener('visibilitychange', handleVisibilityChange)
44+
}, [showNewComments])
45+
11546
if (allNewComments.length === 0) {
11647
return null
11748
}

lib/comments.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import { COMMENT_DEPTH_LIMIT } from './constants'
2+
import { commentUpdateFragment, getLatestCommentCreatedAt } from '../components/use-live-comments'
3+
import { commentsViewedAfterComment } from './new-comments'
4+
import { COMMENT_WITH_NEW_RECURSIVE } from '../fragments/comments'
5+
16
export function updateAncestorsCommentCount (cache, ancestors, increment) {
27
// update all ancestors
38
ancestors.forEach(id => {
@@ -12,3 +17,84 @@ export function updateAncestorsCommentCount (cache, ancestors, increment) {
1217
})
1318
})
1419
}
20+
21+
// live comments - cache manipulations
22+
export function dedupeNewComments (newComments, comments) {
23+
const existingIds = new Set(comments.map(c => c.id))
24+
return newComments.filter(id => !existingIds.has(id))
25+
}
26+
27+
export function collectAllNewComments (item, currentDepth = 1) {
28+
const { COMMENT_DEPTH_LIMIT } = require('./constants')
29+
30+
const allNewComments = [...(item.newComments || [])]
31+
if (item.comments?.comments && currentDepth < (COMMENT_DEPTH_LIMIT - 1)) {
32+
for (const comment of item.comments.comments) {
33+
allNewComments.push(...collectAllNewComments(comment, currentDepth + 1))
34+
}
35+
}
36+
return allNewComments
37+
}
38+
39+
export function prepareComments (client, newComments) {
40+
return (data) => {
41+
// newComments is an array of comment ids that allows us
42+
// to read the latest newComments from the cache, guaranteeing that we're not reading stale data
43+
const freshNewComments = newComments.map(id => {
44+
const fragment = client.cache.readFragment({
45+
id: `Item:${id}`,
46+
fragment: COMMENT_WITH_NEW_RECURSIVE,
47+
fragmentName: 'CommentWithNewRecursive'
48+
})
49+
50+
if (!fragment) {
51+
return null
52+
}
53+
54+
return fragment
55+
}).filter(Boolean)
56+
57+
// count the total number of new comments including its nested new comments
58+
let totalNComments = freshNewComments.length
59+
for (const comment of freshNewComments) {
60+
totalNComments += (comment.ncomments || 0)
61+
}
62+
63+
// update all ancestors, but not the item itself
64+
const ancestors = data.path.split('.').slice(0, -1)
65+
updateAncestorsCommentCount(client.cache, ancestors, totalNComments)
66+
67+
// update commentsViewedAt with the most recent fresh new comment
68+
// quirk: this is not the most recent comment, it's the most recent comment in the freshNewComments array
69+
// as such, the next visit will not outline other new comments that have not been injected yet
70+
const latestCommentCreatedAt = getLatestCommentCreatedAt(freshNewComments, data.createdAt)
71+
const rootId = data.path.split('.')[0]
72+
commentsViewedAfterComment(rootId, latestCommentCreatedAt)
73+
74+
return {
75+
...data,
76+
comments: { ...data.comments, comments: [...freshNewComments, ...data.comments.comments] },
77+
ncomments: data.ncomments + totalNComments,
78+
newComments: []
79+
}
80+
}
81+
}
82+
83+
export function showAllNewCommentsRecursively (client, item, currentDepth = 1) {
84+
// handle new comments at this item level
85+
if (item.newComments && item.newComments.length > 0) {
86+
const dedupedNewComments = dedupeNewComments(item.newComments, item.comments?.comments)
87+
88+
if (dedupedNewComments.length > 0) {
89+
const payload = prepareComments(client, dedupedNewComments)
90+
commentUpdateFragment(client, item.id, payload)
91+
}
92+
}
93+
94+
// recursively handle new comments in child comments
95+
if (item.comments?.comments && currentDepth < (COMMENT_DEPTH_LIMIT - 1)) {
96+
for (const childComment of item.comments.comments) {
97+
showAllNewCommentsRecursively(client, childComment, currentDepth + 1)
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)