|
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 | +// recursively collects all new comments from an item and its children |
| 23 | +// by respecting the depth limit, we avoid collecting new comments to inject in places |
| 24 | +// that are too deep in the tree |
| 25 | +function collectAllNewComments (item, currentDepth = 1) { |
| 26 | + const allNewComments = [...(item.newComments || [])] |
| 27 | + if (item.comments?.comments && currentDepth < (COMMENT_DEPTH_LIMIT - 1)) { |
| 28 | + for (const comment of item.comments.comments) { |
| 29 | + allNewComments.push(...collectAllNewComments(comment, currentDepth + 1)) |
| 30 | + } |
| 31 | + } |
| 32 | + return allNewComments |
| 33 | +} |
| 34 | + |
| 35 | +// prepares and creates a new comments fragment for injection into the cache |
| 36 | +// returns a function that can be used to update an item's comments field |
| 37 | +function prepareComments (client, newComments) { |
| 38 | + return (data) => { |
| 39 | + // newComments is an array of comment ids that allows us |
| 40 | + // to read the latest newComments from the cache, guaranteeing that we're not reading stale data |
| 41 | + const freshNewComments = newComments.map(id => { |
| 42 | + const fragment = client.cache.readFragment({ |
| 43 | + id: `Item:${id}`, |
| 44 | + fragment: COMMENT_WITH_NEW_RECURSIVE, |
| 45 | + fragmentName: 'CommentWithNewRecursive' |
| 46 | + }) |
| 47 | + |
| 48 | + if (!fragment) { |
| 49 | + return null |
| 50 | + } |
| 51 | + |
| 52 | + return fragment |
| 53 | + }).filter(Boolean) |
| 54 | + |
| 55 | + // count the total number of new comments including its nested new comments |
| 56 | + let totalNComments = freshNewComments.length |
| 57 | + for (const comment of freshNewComments) { |
| 58 | + totalNComments += (comment.ncomments || 0) |
| 59 | + } |
| 60 | + |
| 61 | + // update all ancestors, but not the item itself |
| 62 | + const ancestors = data.path.split('.').slice(0, -1) |
| 63 | + updateAncestorsCommentCount(client.cache, ancestors, totalNComments) |
| 64 | + |
| 65 | + // update commentsViewedAt with the most recent fresh new comment |
| 66 | + // quirk: this is not the most recent comment, it's the most recent comment in the freshNewComments array |
| 67 | + // as such, the next visit will not outline other new comments that have not been injected yet |
| 68 | + const latestCommentCreatedAt = getLatestCommentCreatedAt(freshNewComments, data.createdAt) |
| 69 | + const rootId = data.path.split('.')[0] |
| 70 | + commentsViewedAfterComment(rootId, latestCommentCreatedAt) |
| 71 | + |
| 72 | + // return the updated item with the new comments injected |
| 73 | + return { |
| 74 | + ...data, |
| 75 | + comments: { ...data.comments, comments: [...freshNewComments, ...data.comments.comments] }, |
| 76 | + ncomments: data.ncomments + totalNComments, |
| 77 | + newComments: [] |
| 78 | + } |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +// recursively processes and displays all new comments for a thread |
| 83 | +// handles comment injection at each level, respecting depth limits |
| 84 | +function showAllNewCommentsRecursively (client, item, currentDepth = 1) { |
| 85 | + // handle new comments at this item level |
| 86 | + if (item.newComments && item.newComments.length > 0) { |
| 87 | + const dedupedNewComments = dedupeNewComments(item.newComments, item.comments?.comments) |
| 88 | + |
| 89 | + if (dedupedNewComments.length > 0) { |
| 90 | + const payload = prepareComments(client, dedupedNewComments) |
| 91 | + commentUpdateFragment(client, item.id, payload) |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + // recursively handle new comments in child comments |
| 96 | + if (item.comments?.comments && currentDepth < (COMMENT_DEPTH_LIMIT - 1)) { |
| 97 | + for (const childComment of item.comments.comments) { |
| 98 | + showAllNewCommentsRecursively(client, childComment, currentDepth + 1) |
| 99 | + } |
| 100 | + } |
| 101 | +} |
| 102 | + |
13 | 103 | export const ShowNewComments = ({ topLevel, sort, comments, itemId, item, setHasNewComments, newComments = [], depth = 1 }) => {
|
14 | 104 | const client = useApolloClient()
|
15 | 105 | // if item is provided, we're showing all new comments for a thread,
|
|
0 commit comments