inform user when there are new comments
This commit is contained in:
parent
0f5fc31803
commit
297270f34d
|
@ -129,6 +129,9 @@ export default function Header ({ sub }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showJobIndicator = sub !== 'jobs' && (!me || me.noteJobIndicator) &&
|
||||||
|
(!lastCheckedJobs || lastCheckedJobs < subLatestPost?.subLatestPost)
|
||||||
|
|
||||||
const NavItems = ({ className }) => {
|
const NavItems = ({ className }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -150,7 +153,7 @@ export default function Header ({ sub }) {
|
||||||
jobs
|
jobs
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
</Link>
|
</Link>
|
||||||
{sub !== 'jobs' && (!me || me.noteJobIndicator) && (!lastCheckedJobs || lastCheckedJobs < subLatestPost?.subLatestPost) &&
|
{showJobIndicator &&
|
||||||
<span className={styles.jobIndicator}>
|
<span className={styles.jobIndicator}>
|
||||||
<span className='invisible'>{' '}</span>
|
<span className='invisible'>{' '}</span>
|
||||||
</span>}
|
</span>}
|
||||||
|
|
|
@ -11,8 +11,9 @@ import { Button } from 'react-bootstrap'
|
||||||
import { TwitterTweetEmbed } from 'react-twitter-embed'
|
import { TwitterTweetEmbed } from 'react-twitter-embed'
|
||||||
import YouTube from 'react-youtube'
|
import YouTube from 'react-youtube'
|
||||||
import useDarkMode from 'use-dark-mode'
|
import useDarkMode from 'use-dark-mode'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Poll from './poll'
|
import Poll from './poll'
|
||||||
|
import { commentsViewed } from '../lib/new-comments'
|
||||||
|
|
||||||
function BioItem ({ item, handleClick }) {
|
function BioItem ({ item, handleClick }) {
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
@ -99,6 +100,10 @@ function ItemText ({ item }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ItemFull ({ item, bio, ...props }) {
|
export default function ItemFull ({ item, bio, ...props }) {
|
||||||
|
useEffect(() => {
|
||||||
|
commentsViewed(item)
|
||||||
|
}, [item.lastCommentAt])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{item.parentId
|
{item.parentId
|
||||||
|
|
|
@ -9,6 +9,8 @@ import Pin from '../svgs/pushpin-fill.svg'
|
||||||
import reactStringReplace from 'react-string-replace'
|
import reactStringReplace from 'react-string-replace'
|
||||||
import Toc from './table-of-contents'
|
import Toc from './table-of-contents'
|
||||||
import PollIcon from '../svgs/bar-chart-horizontal-fill.svg'
|
import PollIcon from '../svgs/bar-chart-horizontal-fill.svg'
|
||||||
|
import { Badge } from 'react-bootstrap'
|
||||||
|
import { newComments } from '../lib/new-comments'
|
||||||
|
|
||||||
export function SearchTitle ({ title }) {
|
export function SearchTitle ({ title }) {
|
||||||
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
|
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
|
||||||
|
@ -34,6 +36,7 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
|
||||||
useState(mine && (Date.now() < editThreshold))
|
useState(mine && (Date.now() < editThreshold))
|
||||||
const [wrap, setWrap] = useState(false)
|
const [wrap, setWrap] = useState(false)
|
||||||
const titleRef = useRef()
|
const titleRef = useRef()
|
||||||
|
const [hasNewComments, setHasNewComments] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setWrap(
|
setWrap(
|
||||||
|
@ -41,6 +44,11 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
|
||||||
titleRef.current.clientHeight)
|
titleRef.current.clientHeight)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// if we are showing toc, then this is a full item
|
||||||
|
setHasNewComments(!toc && newComments(item))
|
||||||
|
}, [item])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{rank
|
{rank
|
||||||
|
@ -82,7 +90,10 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
</>}
|
</>}
|
||||||
<Link href={`/items/${item.id}`} passHref>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
<a title={`${item.commentSats} sats`} className='text-reset'>{item.ncomments} comments</a>
|
<a title={`${item.commentSats} sats`} className='text-reset'>
|
||||||
|
{item.ncomments} comments
|
||||||
|
{hasNewComments && <>{' '}<Badge className={styles.newComment} variant={null}>new</Badge></>}
|
||||||
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -20,6 +20,11 @@ a.title:visited {
|
||||||
flex: 1 0 128px;
|
flex: 1 0 128px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.newComment {
|
||||||
|
color: var(--theme-grey) !important;
|
||||||
|
background: var(--theme-clickToContextColor) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.pin {
|
.pin {
|
||||||
fill: #a5a5a5;
|
fill: #a5a5a5;
|
||||||
margin-right: .2rem;
|
margin-right: .2rem;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import TextareaAutosize from 'react-textarea-autosize'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import FeeButton from './fee-button'
|
import FeeButton from './fee-button'
|
||||||
|
import { commentsViewedAfterComment } from '../lib/new-comments'
|
||||||
|
|
||||||
export const CommentSchema = Yup.object({
|
export const CommentSchema = Yup.object({
|
||||||
text: Yup.string().required('required').trim()
|
text: Yup.string().required('required').trim()
|
||||||
|
@ -57,8 +58,10 @@ export default function Reply ({ item, onSuccess, replyOpen }) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ancestors = item.path.split('.')
|
||||||
|
|
||||||
// update all ancestors
|
// update all ancestors
|
||||||
item.path.split('.').forEach(id => {
|
ancestors.forEach(id => {
|
||||||
cache.modify({
|
cache.modify({
|
||||||
id: `Item:${id}`,
|
id: `Item:${id}`,
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -68,6 +71,11 @@ export default function Reply ({ item, onSuccess, replyOpen }) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 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, createComment.createdAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
const COMMENTS_VIEW_PREFIX = 'commentsViewedAt'
|
||||||
|
const COMMENTS_NUM_PREFIX = 'commentsViewNum'
|
||||||
|
|
||||||
|
export function commentsViewed (item) {
|
||||||
|
if (!item.parentId && item.lastCommentAt) {
|
||||||
|
localStorage.setItem(`${COMMENTS_VIEW_PREFIX}:${item.id}`, new Date(item.lastCommentAt).getTime())
|
||||||
|
localStorage.setItem(`${COMMENTS_NUM_PREFIX}:${item.id}`, item.ncomments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function commentsViewedAfterComment (rootId, createdAt) {
|
||||||
|
localStorage.setItem(`${COMMENTS_VIEW_PREFIX}:${rootId}`, new Date(createdAt).getTime())
|
||||||
|
const existingRootComments = localStorage.getItem(`${COMMENTS_NUM_PREFIX}:${rootId}`) || 0
|
||||||
|
localStorage.setItem(`${COMMENTS_NUM_PREFIX}:${rootId}`, existingRootComments + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newComments (item) {
|
||||||
|
if (!item.parentId) {
|
||||||
|
const commentsViewedAt = localStorage.getItem(`${COMMENTS_VIEW_PREFIX}:${item.id}`)
|
||||||
|
const commentsViewNum = localStorage.getItem(`${COMMENTS_NUM_PREFIX}:${item.id}`)
|
||||||
|
|
||||||
|
if (commentsViewedAt && commentsViewNum) {
|
||||||
|
return commentsViewedAt < new Date(item.lastCommentAt).getTime() || commentsViewNum < item.ncomments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
Loading…
Reference in New Issue