incorporating answer replies into general comments section

This commit is contained in:
ingawei 2022-10-06 17:37:41 -07:00
parent d685f0288b
commit 7e29733144
4 changed files with 195 additions and 111 deletions

View File

@ -9,7 +9,13 @@ export function ReplyToggle(props: {
}) {
const { seeReplies, numComments, onClick } = props
return (
<button className="text-left text-sm text-indigo-600" onClick={onClick}>
<button
className={clsx(
'text-left text-sm text-indigo-600',
numComments === 0 ? 'hidden' : ''
)}
onClick={onClick}
>
<Row className="items-center gap-1">
<div>
{numComments} {numComments === 1 ? 'Reply' : 'Replies'}

View File

@ -37,6 +37,13 @@ import {
} from 'web/hooks/use-persistent-state'
import { safeLocalStorage } from 'web/lib/util/local'
import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
import { connectStorageEmulator } from 'firebase/storage'
import { Avatar } from '../avatar'
import { UserLink } from '../user-link'
import { CopyLinkDateTimeComponent } from '../feed/copy-link-date-time'
import { Linkify } from '../linkify'
import { ArrowRightIcon } from '@heroicons/react/solid'
import Curve from 'web/public/custom-components/curve'
export function ContractTabs(props: {
contract: Contract
@ -149,77 +156,117 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
</Row>
)
// if (contract.outcomeType === 'FREE_RESPONSE') {
// const sortedAnswers = sortBy(
// contract.answers,
// (a) => -getOutcomeProbability(contract, a.id)
// )
// const commentsByOutcome = groupBy(
// sortedComments,
// (c) => c.answerOutcome ?? c.betOutcome ?? '_'
// )
// const generalTopLevelComments = topLevelComments.filter(
// (c) => c.answerOutcome === undefined && c.betId === undefined
// )
// return (
// <>
// <Col className="flex w-full">
// <div className="mb-4 w-full border-gray-200" />
// {sortedAnswers.map((answer) => {
// const answerComments =
// commentsByOutcome[answer.number.toString()] ?? []
// if (answerComments.length > 0) {
// return (
// <div key={answer.id} className="relative pb-4">
// <FeedAnswerCommentGroup
// contract={contract}
// answer={answer}
// answerComments={
// commentsByOutcome[answer.number.toString()] ?? []
// }
// tips={tips}
// />
// </div>
// )
// } else {
// return <></>
// }
// })}
// <ContractCommentInput className="mb-5" contract={contract} />
// {sortRow}
// {generalTopLevelComments.map((comment) => (
// <FeedCommentThread
// key={comment.id}
// contract={contract}
// parentComment={comment}
// threadComments={commentsByParent[comment.id] ?? []}
// tips={tips}
// />
// ))}
// </Col>
// </>
// )
// } else {
return (
<>
{sortRow}
<ContractCommentInput className="mb-5" contract={contract} />
{topLevelComments.map((parent) => (
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
))}
</>
)
// }
if (contract.outcomeType === 'FREE_RESPONSE') {
const sortedAnswers = sortBy(
contract.answers,
(a) => -getOutcomeProbability(contract, a.id)
)
const commentsByOutcome = groupBy(
sortedComments,
(c) => c.answerOutcome ?? c.betOutcome ?? '_'
)
// const generalTopLevelComments = topLevelComments.filter(
// (c) => c.answerOutcome === undefined && c.betId === undefined
// )
// console.log('answer: ', sortedAnswers)
// console.log('comments by outcome:', commentsByOutcome)
return (
<>
{sortRow}
<ContractCommentInput className="mb-5" contract={contract} />
{topLevelComments.map((parent) => {
if (parent.answerOutcome != undefined) {
const answer = sortedAnswers.find(
(answer) => answer.id === parent.answerOutcome
)
if (answer === undefined) {
console.error('Could not find answer that matches ID')
return <></>
} else {
const { username, avatarUrl, name, text } = answer
const answerElementId = `answer-${answer.id}`
return (
<>
<Row className="bg-greyscale-2 w-fit gap-1 rounded-t-xl rounded-bl-xl px-2 py-2">
<div className="ml-2">
<Avatar
username={username}
avatarUrl={avatarUrl}
size="xxs"
/>
</div>
<Col>
<Row className="gap-1">
<div className="text-greyscale-6 text-xs">
<UserLink username={username} name={name} /> answered
<CopyLinkDateTimeComponent
prefix={contract.creatorUsername}
slug={contract.slug}
createdTime={answer.createdTime}
elementId={answerElementId}
/>
</div>
</Row>
<div className="text-greyscale-7 text-sm">{text}</div>
</Col>
</Row>
<Row>
<div className="ml-2">
<Curve size={28} strokeWidth={1} color="#B1B1C7" />
</div>
<div className="w-full pt-1">
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
</div>
</Row>
</>
)
}
} else {
return (
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
)
}
})}
</>
)
} else {
return (
<>
{sortRow}
<ContractCommentInput className="mb-5" contract={contract} />
{topLevelComments.map((parent) => (
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
))}
</>
)
}
})
const BetsTabContent = memo(function BetsTabContent(props: {

View File

@ -18,6 +18,7 @@ import { CommentTipMap } from 'web/hooks/use-tip-txns'
import { UserLink } from 'web/components/user-link'
import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
import { ReplyToggle } from '../comments/comments'
import { ReplyIcon } from '@heroicons/react/solid'
export function FeedAnswerCommentGroup(props: {
contract: FreeResponseContract
@ -62,11 +63,11 @@ export function FeedAnswerCommentGroup(props: {
elementId={answerElementId}
/>
</div>
<Row className="align-items justify-between gap-2 sm:flex-row">
<span className="text-md whitespace-pre-line">
<Linkify text={text} />
</span>
<div>
{/* <Row className="align-items justify-between gap-2 sm:flex-row"> */}
<span className="text-md whitespace-pre-line">
<Linkify text={text} />
</span>
{/* <div>
<button
className="text-xs font-bold text-gray-500 hover:underline"
onClick={() =>
@ -75,13 +76,25 @@ export function FeedAnswerCommentGroup(props: {
>
Reply
</button>
</div> */}
{/* </Row> */}
<Row className="w-full">
<ReplyToggle
seeReplies={seeReplies}
numComments={answerComments.length}
onClick={() => setSeeReplies(!seeReplies)}
/>
<div className="justify-self-end">
<button
className="text-greyscale-5"
onClick={() =>
setReplyTo({ id: answer.id, username: answer.username })
}
>
<ReplyIcon className="h-5 w-5" />
</button>
</div>
</Row>
<ReplyToggle
seeReplies={seeReplies}
numComments={answerComments.length}
onClick={() => setSeeReplies(!seeReplies)}
/>
</Col>
</Row>
{seeReplies && (

View File

@ -1,5 +1,5 @@
import { ContractComment } from 'common/comment'
import { Contract } from 'common/contract'
import { AnyContractType, Contract } from 'common/contract'
import React, { useEffect, useRef, useState } from 'react'
import { useUser } from 'web/hooks/use-user'
import { formatMoney } from 'common/util/format'
@ -115,39 +115,57 @@ export function FeedComment(props: {
</Col>
<Col className="w-full">
<FeedCommentHeader comment={comment} contract={contract} />
<Row>
<Content
className="text-greyscale-7 mt-2 grow text-[14px]"
content={content || text}
smallImage
/>
<Row
className={clsx(
'ml-2 items-center gap-2 text-xs text-gray-500 transition-opacity',
showActions ? '' : 'opacity-0'
)}
>
{onReplyClick && (
<Button
className="font-bold hover:underline"
onClick={onReplyClick}
size="2xs"
color="gray-white"
>
<ReplyIcon className="h-5 w-5" />
</Button>
)}
{tips && <Tipper comment={comment} tips={tips} />}
{(contract.openCommentBounties ?? 0) > 0 && (
<AwardBountyButton comment={comment} contract={contract} />
)}
</Row>
</Row>
{/* TODO: bug where if this is iFrame, it does not scroll */}
<Content
className="text-greyscale-7 mt-2 grow text-[14px]"
content={content || text}
smallImage
/>
<CommentActions
showActions={showActions}
onReplyClick={onReplyClick}
tips={tips}
comment={comment}
contract={contract}
/>
</Col>
</Row>
)
}
export function CommentActions(props: {
showActions: boolean
onReplyClick?: () => void
tips?: CommentTips | undefined
comment: ContractComment
contract: Contract<AnyContractType>
}) {
const { showActions, onReplyClick, tips, comment, contract } = props
return (
<Row
className={clsx(
'ml-2 items-center justify-end gap-2 text-xs text-gray-500 transition-opacity',
showActions ? '' : 'md:opacity-0'
)}
>
{onReplyClick && (
<Button
className="font-bold hover:underline"
onClick={onReplyClick}
size="2xs"
color="gray-white"
>
<ReplyIcon className="h-5 w-5" />
</Button>
)}
{tips && <Tipper comment={comment} tips={tips} />}
{(contract.openCommentBounties ?? 0) > 0 && (
<AwardBountyButton comment={comment} contract={contract} />
)}
</Row>
)
}
function CommentStatus(props: {
contract: Contract
outcome: string