diff --git a/components/header.js b/components/header.js index 66593564..30316f75 100644 --- a/components/header.js +++ b/components/header.js @@ -129,6 +129,9 @@ export default function Header ({ sub }) { } } + const showJobIndicator = sub !== 'jobs' && (!me || me.noteJobIndicator) && + (!lastCheckedJobs || lastCheckedJobs < subLatestPost?.subLatestPost) + const NavItems = ({ className }) => { return ( <> @@ -150,7 +153,7 @@ export default function Header ({ sub }) { jobs - {sub !== 'jobs' && (!me || me.noteJobIndicator) && (!lastCheckedJobs || lastCheckedJobs < subLatestPost?.subLatestPost) && + {showJobIndicator && {' '} } diff --git a/components/item-full.js b/components/item-full.js index 562d9e1a..60f6c1f2 100644 --- a/components/item-full.js +++ b/components/item-full.js @@ -11,8 +11,9 @@ import { Button } from 'react-bootstrap' import { TwitterTweetEmbed } from 'react-twitter-embed' import YouTube from 'react-youtube' import useDarkMode from 'use-dark-mode' -import { useState } from 'react' +import { useEffect, useState } from 'react' import Poll from './poll' +import { commentsViewed } from '../lib/new-comments' function BioItem ({ item, handleClick }) { const me = useMe() @@ -99,6 +100,10 @@ function ItemText ({ item }) { } export default function ItemFull ({ item, bio, ...props }) { + useEffect(() => { + commentsViewed(item) + }, [item.lastCommentAt]) + return ( <> {item.parentId diff --git a/components/item.js b/components/item.js index a4b7c31c..f41559c2 100644 --- a/components/item.js +++ b/components/item.js @@ -9,6 +9,8 @@ import Pin from '../svgs/pushpin-fill.svg' import reactStringReplace from 'react-string-replace' import Toc from './table-of-contents' import PollIcon from '../svgs/bar-chart-horizontal-fill.svg' +import { Badge } from 'react-bootstrap' +import { newComments } from '../lib/new-comments' export function SearchTitle ({ title }) { 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)) const [wrap, setWrap] = useState(false) const titleRef = useRef() + const [hasNewComments, setHasNewComments] = useState(false) useEffect(() => { setWrap( @@ -41,6 +44,11 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) { titleRef.current.clientHeight) }, []) + useEffect(() => { + // if we are showing toc, then this is a full item + setHasNewComments(!toc && newComments(item)) + }, [item]) + return ( <> {rank @@ -82,7 +90,10 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) { \ } - {item.ncomments} comments + + {item.ncomments} comments + {hasNewComments && <>{' '}new} + \ diff --git a/components/item.module.css b/components/item.module.css index 75fe64ab..41b8515d 100644 --- a/components/item.module.css +++ b/components/item.module.css @@ -20,6 +20,11 @@ a.title:visited { flex: 1 0 128px; } +.newComment { + color: var(--theme-grey) !important; + background: var(--theme-clickToContextColor) !important; +} + .pin { fill: #a5a5a5; margin-right: .2rem; diff --git a/components/reply.js b/components/reply.js index 4239e698..44fade81 100644 --- a/components/reply.js +++ b/components/reply.js @@ -8,6 +8,7 @@ import TextareaAutosize from 'react-textarea-autosize' import { useEffect, useState } from 'react' import Link from 'next/link' import FeeButton from './fee-button' +import { commentsViewedAfterComment } from '../lib/new-comments' export const CommentSchema = Yup.object({ 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 - item.path.split('.').forEach(id => { + ancestors.forEach(id => { cache.modify({ id: `Item:${id}`, 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) } } ) diff --git a/lib/new-comments.js b/lib/new-comments.js new file mode 100644 index 00000000..9e63ad86 --- /dev/null +++ b/lib/new-comments.js @@ -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 +}