import Link from 'next/link' import styles from './item.module.css' import UpVote from './upvote' import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react' import { USER_ID, UNKNOWN_LINK_REL } from '@/lib/constants' import Pin from '@/svgs/pushpin-fill.svg' import reactStringReplace from 'react-string-replace' import PollIcon from '@/svgs/bar-chart-horizontal-fill.svg' import BountyIcon from '@/svgs/bounty-bag.svg' import ActionTooltip from './action-tooltip' import ImageIcon from '@/svgs/image-fill.svg' import { numWithUnits } from '@/lib/format' import ItemInfo from './item-info' import Prism from '@/svgs/prism.svg' import { commentsViewedAt } from '@/lib/new-comments' import { useRouter } from 'next/router' import { Badge } from 'react-bootstrap' import AdIcon from '@/svgs/advertisement-fill.svg' import { DownZap } from './dont-link-this' import { timeLeft } from '@/lib/time' import classNames from 'classnames' import removeMd from 'remove-markdown' function onItemClick (e, router, item) { const viewedAt = commentsViewedAt(item) if (viewedAt) { e.preventDefault() if (e.ctrlKey || e.metaKey) { window.open( `/items/${item.id}`, '_blank', 'noopener,noreferrer' ) } else { router.push( `/items/${item.id}?commentsViewedAt=${viewedAt}`, `/items/${item.id}`) } } } export function SearchTitle ({ title }) { return reactStringReplace(title, /\*\*\*([^*]+)\*\*\*/g, (match, i) => { return {match} }) } const ItemContext = createContext({ pendingSats: 0, setPendingSats: undefined, pendingVote: undefined, setPendingVote: undefined, pendingDownSats: 0, setPendingDownSats: undefined }) export const ItemContextProvider = ({ children }) => { const parentCtx = useItemContext() const [pendingSats, innerSetPendingSats] = useState(0) const [pendingCommentSats, innerSetPendingCommentSats] = useState(0) const [pendingVote, setPendingVote] = useState() const [pendingDownSats, setPendingDownSats] = useState(0) // cascade comment sats up to root context const setPendingSats = useCallback((sats) => { innerSetPendingSats(sats) parentCtx?.setPendingCommentSats?.(sats) }, [parentCtx?.setPendingCommentSats]) const setPendingCommentSats = useCallback((sats) => { innerSetPendingCommentSats(sats) parentCtx?.setPendingCommentSats?.(sats) }, [parentCtx?.setPendingCommentSats]) const value = useMemo(() => ({ pendingSats, setPendingSats, pendingCommentSats, setPendingCommentSats, pendingVote, setPendingVote, pendingDownSats, setPendingDownSats }), [pendingSats, setPendingSats, pendingCommentSats, setPendingCommentSats, pendingVote, setPendingVote, pendingDownSats, setPendingDownSats]) return {children} } export const useItemContext = () => { return useContext(ItemContext) } export default function Item ({ item, rank, belowTitle, right, full, children, siblingComments, onQuoteReply, pinnable }) { const titleRef = useRef() const router = useRouter() const image = item.url && item.url.startsWith(process.env.NEXT_PUBLIC_IMGPROXY_URL) return ( {rank ? (
{rank}
) :
}
onItemClick(e, router, item)} ref={titleRef} className={`${styles.title} text-reset me-2`} > {item.searchTitle ? : item.title} {item.pollCost && } {item.bounty > 0 && } {item.forwards?.length > 0 && } {image && } {item.url && !image && // eslint-disable-next-line {item.url.replace(/(^https?:|^)\/\//, '')} }
AD} /> {belowTitle}
{right}
{children && (
{children}
)} ) } export function ItemSummary ({ item }) { const router = useRouter() const link = ( onItemClick(e, router, item)} className={`${item.title && styles.title} ${styles.summaryText} text-reset me-2`} > {item.title ?? removeMd(item.text)} ) const info = ( AD} /> ) return (
{item.title ? ( <> {link} {info} ) : ( <> {info} {link} )}
) } export function ItemSkeleton ({ rank, children, showUpvote = true }) { return ( <> {rank ? (
{rank}
) :
}
{showUpvote && }
{children && (
{children}
)} ) } function ZapIcon ({ item, pinnable }) { const { pendingSats, pendingDownSats } = useItemContext() const meSats = item.meSats + pendingSats const downSats = item.meDontLikeSats + pendingDownSats return item.position && (pinnable || !item.subName) ? : downSats > meSats ? : Number(item.user?.id) === USER_ID.ad ? : } function PollIndicator ({ item }) { const hasExpiration = !!item.pollExpiresAt const timeRemaining = timeLeft(new Date(item.pollExpiresAt)) const isActive = !hasExpiration || !!timeRemaining return ( ) }