import itemStyles from './item.module.css' import styles from './comment.module.css' import Text, { SearchText } from './text' import Link from 'next/link' import Reply, { ReplyOnAnotherPage } from './reply' import { useEffect, useMemo, useRef, useState } from 'react' import UpVote from './upvote' import Eye from '@/svgs/eye-fill.svg' import EyeClose from '@/svgs/eye-close-line.svg' import { useRouter } from 'next/router' import CommentEdit from './comment-edit' import { ANON_USER_ID, COMMENT_DEPTH_LIMIT, UNKNOWN_LINK_REL } from '@/lib/constants' import { ignoreClick } from '@/lib/clicks' import PayBounty from './pay-bounty' import BountyIcon from '@/svgs/bounty-bag.svg' import ActionTooltip from './action-tooltip' import { numWithUnits } from '@/lib/format' import Share from './share' import ItemInfo from './item-info' import Badge from 'react-bootstrap/Badge' import { RootProvider, useRoot } from './root' import { useMe } from './me' import { useQuoteReply } from './use-quote-reply' import { DownZap } from './dont-link-this' import Skull from '@/svgs/death-skull.svg' import { commentSubTreeRootId } from '@/lib/item' import Pin from '@/svgs/pushpin-fill.svg' function Parent ({ item, rootText }) { const root = useRoot() const ParentFrag = () => ( <> \ parent ) return ( <> {Number(root.id) !== Number(item.parentId) && } \ {rootText || 'on:'} {root?.title} {root.subName && {' '}{root.subName} } ) } const truncateString = (string = '', maxLength = 140) => string.length > maxLength ? `${string.substring(0, maxLength)} […]` : string export function CommentFlat ({ item, rank, siblingComments, ...props }) { const router = useRouter() const [href, as] = useMemo(() => { const rootId = commentSubTreeRootId(item) return [{ pathname: '/items/[id]', query: { id: rootId, commentId: item.id } }, `/items/${rootId}`] }, [item?.id]) return ( <> {rank ? (
{rank}
) :
}
{ if (ignoreClick(e)) return router.push(href, as) }} >
) } export default function Comment ({ item, children, replyOpen, includeParent, topLevel, rootText, noComments, noReply, truncate, depth, pin }) { const [edit, setEdit] = useState() const me = useMe() const isHiddenFreebie = !me?.privates?.wildWestMode && !me?.privates?.greeterMode && !item.mine && item.freebie && !item.freedFreebie const [collapse, setCollapse] = useState( (isHiddenFreebie || item?.user?.meMute || (item?.outlawed && !me?.privates?.wildWestMode)) && !includeParent ? 'yep' : 'nope') const ref = useRef(null) const router = useRouter() const root = useRoot() const { ref: textRef, quote, quoteReply, cancelQuote } = useQuoteReply({ text: item.text }) useEffect(() => { setCollapse(window.localStorage.getItem(`commentCollapse:${item.id}`) || collapse) if (Number(router.query.commentId) === Number(item.id)) { // HACK wait for other comments to collapse if they're collapsed setTimeout(() => { ref.current.scrollIntoView({ behavior: 'instant', block: 'start' }) ref.current.classList.add('outline-it') }, 100) } }, [item.id, router.query.commentId]) useEffect(() => { if (router.query.commentsViewedAt && me?.id !== item.user?.id && new Date(item.createdAt).getTime() > router.query.commentsViewedAt) { ref.current.classList.add('outline-new-comment') } }, [item.id]) const bottomedOut = depth === COMMENT_DEPTH_LIMIT // Don't show OP badge when anon user comments on anon user posts const op = root.user.name === item.user.name && Number(item.user.id) !== ANON_USER_ID ? 'OP' : root.forwards?.some(f => f.user.name === item.user.name) && Number(item.user.id) !== ANON_USER_ID ? 'fwd' : null const bountyPaid = root.bountyPaidTo?.includes(Number(item.id)) return (
ref.current.classList.add('outline-new-comment-unset')} onTouchStart={() => ref.current.classList.add('outline-new-comment-unset')} >
{item.outlawed && !me?.privates?.wildWestMode ? : item.meDontLikeSats > item.meSats ? : pin ? : }
{item.user?.meMute && !includeParent && collapse === 'yep' ? ( { setCollapse('nope') window.localStorage.setItem(`commentCollapse:${item.id}`, 'nope') }} >reply from someone you muted ) : {op}} onQuoteReply={quoteReply} nested={!includeParent} extraInfo={ <> {includeParent && } {bountyPaid && } } onEdit={e => { setEdit(!edit) }} editText={edit ? 'cancel' : 'edit'} />} {!includeParent && (collapse === 'yep' ? { setCollapse('nope') window.localStorage.setItem(`commentCollapse:${item.id}`, 'nope') }} /> : { setCollapse('yep') window.localStorage.setItem(`commentCollapse:${item.id}`, 'yep') }} />)} {topLevel && ( )}
{edit ? ( { setEdit(!edit) }} /> ) : (
{item.searchText ? : ( {item.outlawed && !me?.privates?.wildWestMode ? '*stackers have outlawed this. turn on wild west mode in your [settings](/settings) to see outlawed content.*' : truncate ? truncateString(item.text) : item.text} )}
)}
{collapse !== 'yep' && ( bottomedOut ?
: (
{item.outlawed && !me?.privates?.wildWestMode ?
: !noReply && {root.bounty && !bountyPaid && } } {children}
{item.comments && !noComments ? item.comments.map((item) => ( )) : null}
) )}
) } export function CommentSkeleton ({ skeletonChildren }) { return (
{skeletonChildren ? : null}
) }