* check new comments every 10 seconds * enhance: clear newComments on child comments when we show a topLevel new comment; cleanup: resolvers, logs * handle comments of comments, new structure to clear newComments on childs * use original recursive comments data structure * correct comment structure after deduplication * faster newComments query deduplication, don't need to know how many comments are there * cleanup: comments on newComments fetches and dedupes * cleanup, use correct function declarations * stop polling after 30 minutes, pause polling if user is not on the page * ActionTooltip indicating that the user is in a live comment section * handleVisibilityChange to control polling by visibility * paused polling styling, check activity on 1 minute intervals and visibility change, light cleanup * user can resume polling without refreshing the page * better naming, straightforward dedupeComment on newComment arrival * cleanup: better naming, get latest comment creation, correct order of comment injection * cleanup: refactor live comments related functions to use-live-comments.js * refactor: clearer naming, optimized polling and date retrieval logic, use of constants, general cleanup * ui: place ShowNewComments in the bottom-right corner of nested comments * fix: make updateQuery sort-aware to correctly inject the comment in the correct Item query * cleanup: better naming; fix: usecallback on live comments component; fix leak on useEffect because of missing sort atomic apollo cache manipulations; manage top sort not being present in item query cache queue nested comments without a parent, retry on the next poll fix commit messages * fix: don't show unpaid comments; cleanup: compact cache merge/dedupe, queue comments via state * fix: read new comments fragments to inject fresh new comments, fixing dropped comments; ui: show amount of new comments refactor: correct function positioning; cleanup: useless logs * enhance: queuedComments Ref, cache-and-network fetch policy; freshNewComments readFragment fallback to received comment * cleanup: detailed comments and better ShowNewComment text * fix: while showing new comments, also update ncomments for UI and pagination * refactor: ShowNewComments is its own component; cleanup: proven useless dedupe on ShowNewComments, count nested ncomments from fresh new comments * enhance: direct latest comment createdAt calc with reduce * cleanup queue on unmount * feat: live comments indicator for bottomed-out replies, ncomments updates; fix: nested comment structures - new comments indicator for bottomed-out replies - ncomments sync for parent and its ancestors - limited comments fragment for comments that don't have CommentsRecursive - reduce cache complexity by removing useless roundtrips ux: live comments indicator on bottomedOut replies fix: dedupe newComments before displaying ShowNewComments to avoid false positives enhance: store ids of new comments in the cache, instead of carrying full comments that would get discarded anyway hotfix: newComments deduplication ID mismatch, filter null comments from freshNewComments fix: ncomments not updating for all comment levels; refactor: share Reply update ancestors' ncomments function with ShowNewComments cleanup: better naming to indicate the total number of comments including nested comments fix: increment parent comment ncomments cleanup: Items that will have comments will always have a structure where item.comments is true cleanup: reduce code complexity checking the nested comment update result instead of preventively reading the fragment cleanup: avoid double-updating ncomments on parent fix: don't use CommentsRecursive for bottomed-out comments cleanup: better fragment naming; add TODO for absolute bottom comments * backport live comments logic enhancements use-live-comments: - remove useless dedupe against already present comments - check newComments.comments length to tell if there are new comments - code reordering show-new-comments: - show all new comments recursively for nested comments - get always the newest comments to inject also their own child new comments - update local storage commentsViewedAt on comment injection - respect depth on comment injection comments.js - apollo cache manipulations now live here * hotfix: handle undefined item.comments.comments on dedupe * hotfix: limited fragment for recursive comment collection; protect from null fragments; add missing deps to memoization * docs: clarify ncomments updates * cleanup: remove unused export * count and show only the direct new comments and recursively their children enhance: dedupe against existing comments only in the component enhance: recursive count/injection share the same logic * fix regression on top level counting * hotfix: introduce readNestedCommentsFragment in lib/comments.js * fix: count also existing comments of a new comment; cleanup: use readCommentFragment also for prepareComments; reduce freshNewComments usage * add support for comments at the deepest level fixes: - client-side navigation re-fetched all new comments because 'after' was cached, now the latest new comment time persists in sessionStorage enhancements: - use CommentWithNewMinimal fragment fallback for comments at the deepest level - tweak ReplyOnAnotherPage to show also how many direct new comments are there cleanup: - queue management is not needed anymore, therefore it has been removed * cleanup: remove logs * revert counting on ReplyOnAnotherPage, TODO for enhancements PR * move ShowNewComments to CommentsHeader for top level comments * fix: update commentsViewedAfterComment to support ncomments * fix typo, lint * cleanup: remove old CSS * enhance: inject topLevel and its children new comments, simplify injection logic - top-level and nested comment handling share the same recursion logic - ShowNewComments references the item object for every type of comments — note: item from item-full.js is passed to comments.js - depth now starts at 0 to support top level comments - injection and counting now reach the deepest level, updating also the deepest comment * cleanup: remove unused topLevel prop * fix: deepest comments don't have CommentsRecursive structure, don't access it on injection * move top level ShowNewComments above CommentsHeader; preserve space to avoid vertical layout shifting * cleanup: remove unused item on CommentsHeader --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
191 lines
6.1 KiB
JavaScript
191 lines
6.1 KiB
JavaScript
import { Form, MarkdownInput } from '@/components/form'
|
|
import styles from './reply.module.css'
|
|
import { COMMENTS } from '@/fragments/comments'
|
|
import { useMe } from './me'
|
|
import { forwardRef, useCallback, useEffect, useState, useRef, useMemo } from 'react'
|
|
import { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
|
|
import { commentsViewedAfterComment } from '@/lib/new-comments'
|
|
import { commentSchema } from '@/lib/validate'
|
|
import { ItemButtonBar } from './post'
|
|
import { useShowModal } from './modal'
|
|
import { Button } from 'react-bootstrap'
|
|
import { useRoot } from './root'
|
|
import { CREATE_COMMENT } from '@/fragments/paidAction'
|
|
import useItemSubmit from './use-item-submit'
|
|
import gql from 'graphql-tag'
|
|
import { updateAncestorsCommentCount } from '@/lib/comments'
|
|
|
|
export default forwardRef(function Reply ({
|
|
item,
|
|
replyOpen,
|
|
children,
|
|
onQuoteReply,
|
|
onCancelQuote,
|
|
quote
|
|
}, ref) {
|
|
const [reply, setReply] = useState(replyOpen || quote)
|
|
const { me } = useMe()
|
|
const parentId = item.id
|
|
const replyInput = useRef(null)
|
|
const showModal = useShowModal()
|
|
const root = useRoot()
|
|
const sub = item?.sub || root?.sub
|
|
|
|
useEffect(() => {
|
|
if (replyOpen || quote || !!window.localStorage.getItem('reply-' + parentId + '-' + 'text')) {
|
|
setReply(true)
|
|
}
|
|
}, [replyOpen, quote, parentId])
|
|
|
|
const placeholder = useMemo(() => {
|
|
return [
|
|
'comment for currency',
|
|
'fractions of a penny for your thoughts?',
|
|
'put your money where your mouth is'
|
|
][parentId % 3]
|
|
}, [parentId])
|
|
|
|
const onSubmit = useItemSubmit(CREATE_COMMENT, {
|
|
extraValues: { parentId },
|
|
paidMutationOptions: {
|
|
update (cache, { data: { upsertComment: { result, invoice } } }) {
|
|
if (!result) return
|
|
|
|
cache.modify({
|
|
id: `Item:${parentId}`,
|
|
fields: {
|
|
comments (existingComments = {}) {
|
|
const newCommentRef = cache.writeFragment({
|
|
data: result,
|
|
fragment: COMMENTS,
|
|
fragmentName: 'CommentsRecursive'
|
|
})
|
|
return {
|
|
cursor: existingComments.cursor,
|
|
comments: [newCommentRef, ...(existingComments?.comments || [])]
|
|
}
|
|
}
|
|
},
|
|
optimistic: true
|
|
})
|
|
|
|
// no lag for itemRepetition
|
|
if (!item.mine && me) {
|
|
cache.updateQuery({
|
|
query: gql`{ itemRepetition(parentId: "${parentId}") }`
|
|
}, data => {
|
|
return {
|
|
itemRepetition: (data?.itemRepetition || 0) + 1
|
|
}
|
|
})
|
|
}
|
|
|
|
const ancestors = item.path.split('.')
|
|
|
|
// update all ancestors
|
|
updateAncestorsCommentCount(cache, ancestors, 1)
|
|
|
|
// so that we don't see indicator for our own comments, we record this comments as the latest time
|
|
// but we also have record num comments, in case someone else commented when we did
|
|
const root = ancestors[0]
|
|
commentsViewedAfterComment(root, result.createdAt)
|
|
}
|
|
},
|
|
onSuccessfulSubmit: (data, { resetForm }) => {
|
|
resetForm({ values: { text: '' } })
|
|
setReply(replyOpen || false)
|
|
},
|
|
navigateOnSubmit: false
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (replyInput.current && reply && !replyOpen) replyInput.current.focus()
|
|
}, [reply])
|
|
|
|
const onCancel = useCallback(() => {
|
|
window.localStorage.removeItem('reply-' + parentId + '-' + 'text')
|
|
setReply(false)
|
|
onCancelQuote?.()
|
|
}, [setReply, parentId, onCancelQuote])
|
|
|
|
return (
|
|
<div>
|
|
{replyOpen
|
|
? <div className='p-3' />
|
|
: (
|
|
<div className={styles.replyButtons}>
|
|
<div
|
|
className='pe-3'
|
|
onClick={e => {
|
|
if (reply) {
|
|
const text = window.localStorage.getItem('reply-' + parentId + '-' + 'text')
|
|
if (text?.trim()) {
|
|
showModal(onClose => (
|
|
<>
|
|
<p className='fw-bolder'>Are you sure? You will lose your work</p>
|
|
<div className='d-flex justify-content-end'>
|
|
<Button
|
|
variant='info' onClick={() => {
|
|
onCancel()
|
|
onClose()
|
|
}}
|
|
>yep
|
|
</Button>
|
|
</div>
|
|
</>
|
|
))
|
|
} else {
|
|
onCancel()
|
|
}
|
|
} else {
|
|
e.preventDefault()
|
|
onQuoteReply?.({ selectionOnly: true })
|
|
setReply(true)
|
|
}
|
|
}}
|
|
>
|
|
{reply ? 'cancel' : 'reply'}
|
|
</div>
|
|
{/* HACK if we need more items, we should probably do a comment toolbar */}
|
|
{children}
|
|
</div>)}
|
|
{reply &&
|
|
<div className={styles.reply}>
|
|
<FeeButtonProvider
|
|
baseLineItems={postCommentBaseLineItems({ baseCost: sub?.replyCost ?? 1, comment: true, me: !!me })}
|
|
useRemoteLineItems={postCommentUseRemoteLineItems({ parentId: item.id, me: !!me })}
|
|
>
|
|
<Form
|
|
initial={{
|
|
text: ''
|
|
}}
|
|
schema={commentSchema}
|
|
onSubmit={onSubmit}
|
|
storageKeyPrefix={`reply-${parentId}`}
|
|
>
|
|
<MarkdownInput
|
|
name='text'
|
|
minRows={6}
|
|
autoFocus={!replyOpen}
|
|
required
|
|
appendValue={quote}
|
|
placeholder={placeholder}
|
|
hint={sub?.moderated && 'this territory is moderated'}
|
|
/>
|
|
<ItemButtonBar createText='reply' hasCancel={false} />
|
|
</Form>
|
|
</FeeButtonProvider>
|
|
</div>}
|
|
</div>
|
|
)
|
|
})
|
|
|
|
export function ReplySkeleton () {
|
|
return (
|
|
<div className={`${styles.reply} ${styles.skeleton}`}>
|
|
<div className={`${styles.input} clouds`} />
|
|
<div className={`${styles.button} clouds`} />
|
|
</div>
|
|
)
|
|
}
|