Fr comment ux improvements (#451)
* Extend comment input box, only use airplane * Only 1 commentable bet, shrink input, fix feed lines * Pad sign in to comment button * Small changes
This commit is contained in:
parent
ad6594f0bc
commit
7e37fc776c
|
@ -29,7 +29,6 @@ export type CommentInputItem = BaseActivityItem & {
|
||||||
type: 'commentInput'
|
type: 'commentInput'
|
||||||
betsByCurrentUser: Bet[]
|
betsByCurrentUser: Bet[]
|
||||||
commentsByCurrentUser: Comment[]
|
commentsByCurrentUser: Comment[]
|
||||||
answerOutcome?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DescriptionItem = BaseActivityItem & {
|
export type DescriptionItem = BaseActivityItem & {
|
||||||
|
@ -74,10 +73,10 @@ export type BetGroupItem = BaseActivityItem & {
|
||||||
|
|
||||||
export type AnswerGroupItem = BaseActivityItem & {
|
export type AnswerGroupItem = BaseActivityItem & {
|
||||||
type: 'answergroup'
|
type: 'answergroup'
|
||||||
|
user: User | undefined | null
|
||||||
answer: Answer
|
answer: Answer
|
||||||
items: ActivityItem[]
|
comments: Comment[]
|
||||||
betsByCurrentUser?: Bet[]
|
bets: Bet[]
|
||||||
commentsByCurrentUser?: Comment[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CloseItem = BaseActivityItem & {
|
export type CloseItem = BaseActivityItem & {
|
||||||
|
@ -232,31 +231,19 @@ function getAnswerGroups(
|
||||||
|
|
||||||
const answerGroups = outcomes
|
const answerGroups = outcomes
|
||||||
.map((outcome) => {
|
.map((outcome) => {
|
||||||
const answerBets = bets.filter((bet) => bet.outcome === outcome)
|
|
||||||
const answerComments = comments.filter((comment) =>
|
|
||||||
answerBets.some((bet) => bet.id === comment.betId)
|
|
||||||
)
|
|
||||||
const answer = contract.answers?.find(
|
const answer = contract.answers?.find(
|
||||||
(answer) => answer.id === outcome
|
(answer) => answer.id === outcome
|
||||||
) as Answer
|
) as Answer
|
||||||
|
|
||||||
let items = groupBets(answerBets, answerComments, contract, user?.id, {
|
// TODO: this doesn't abbreviate these groups for activity feed anymore
|
||||||
hideOutcome: true,
|
|
||||||
abbreviated,
|
|
||||||
smallAvatar: true,
|
|
||||||
reversed,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (abbreviated)
|
|
||||||
items = items.slice(-ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: outcome,
|
id: outcome,
|
||||||
type: 'answergroup' as const,
|
type: 'answergroup' as const,
|
||||||
contract,
|
contract,
|
||||||
answer,
|
|
||||||
items,
|
|
||||||
user,
|
user,
|
||||||
|
answer,
|
||||||
|
comments,
|
||||||
|
bets,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((group) => group.answer)
|
.filter((group) => group.answer)
|
||||||
|
@ -276,7 +263,6 @@ function getAnswerAndCommentInputGroups(
|
||||||
outcomes = sortBy(outcomes, (outcome) =>
|
outcomes = sortBy(outcomes, (outcome) =>
|
||||||
getOutcomeProbability(contract, outcome)
|
getOutcomeProbability(contract, outcome)
|
||||||
)
|
)
|
||||||
const betsByCurrentUser = bets.filter((bet) => bet.userId === user?.id)
|
|
||||||
|
|
||||||
const answerGroups = outcomes
|
const answerGroups = outcomes
|
||||||
.map((outcome) => {
|
.map((outcome) => {
|
||||||
|
@ -284,25 +270,14 @@ function getAnswerAndCommentInputGroups(
|
||||||
(answer) => answer.id === outcome
|
(answer) => answer.id === outcome
|
||||||
) as Answer
|
) as Answer
|
||||||
|
|
||||||
const answerBets = bets.filter((bet) => bet.outcome === outcome)
|
|
||||||
const answerComments = comments.filter(
|
|
||||||
(comment) =>
|
|
||||||
comment.answerOutcome === outcome ||
|
|
||||||
answerBets.some((bet) => bet.id === comment.betId)
|
|
||||||
)
|
|
||||||
const items = getCommentThreads(bets, answerComments, contract)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: outcome,
|
id: outcome,
|
||||||
type: 'answergroup' as const,
|
type: 'answergroup' as const,
|
||||||
contract,
|
contract,
|
||||||
answer,
|
|
||||||
items,
|
|
||||||
user,
|
user,
|
||||||
betsByCurrentUser,
|
answer,
|
||||||
commentsByCurrentUser: answerComments.filter(
|
comments,
|
||||||
(comment) => comment.userId === user?.id
|
bets,
|
||||||
),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((group) => group.answer) as ActivityItem[]
|
.filter((group) => group.answer) as ActivityItem[]
|
||||||
|
@ -425,13 +400,6 @@ export function getAllContractActivityItems(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
items.push({
|
|
||||||
type: 'commentInput' as const,
|
|
||||||
id: 'commentInput',
|
|
||||||
contract,
|
|
||||||
betsByCurrentUser: [],
|
|
||||||
commentsByCurrentUser: [],
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
items.push(
|
items.push(
|
||||||
...groupBetsAndComments(bets, comments, contract, user?.id, {
|
...groupBetsAndComments(bets, comments, contract, user?.id, {
|
||||||
|
@ -450,16 +418,6 @@ export function getAllContractActivityItems(
|
||||||
items.push({ type: 'resolve', id: `${contract.resolutionTime}`, contract })
|
items.push({ type: 'resolve', id: `${contract.resolutionTime}`, contract })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outcomeType === 'BINARY') {
|
|
||||||
items.push({
|
|
||||||
type: 'commentInput' as const,
|
|
||||||
id: 'commentInput',
|
|
||||||
contract,
|
|
||||||
betsByCurrentUser: [],
|
|
||||||
commentsByCurrentUser: [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reversed) items.reverse()
|
if (reversed) items.reverse()
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { Answer } from 'common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { ActivityItem } from 'web/components/feed/activity-items'
|
|
||||||
import { Bet } from 'common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { Comment } from 'common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
|
||||||
import { getDpmOutcomeProbability } from 'common/calculate-dpm'
|
import { getDpmOutcomeProbability } from 'common/calculate-dpm'
|
||||||
import { formatPercent } from 'common/util/format'
|
import { formatPercent } from 'common/util/format'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
@ -16,44 +14,80 @@ import { Linkify } from 'web/components/linkify'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||||
import { BuyButton } from 'web/components/yes-no-selector'
|
import { BuyButton } from 'web/components/yes-no-selector'
|
||||||
import { FeedItem } from 'web/components/feed/feed-items'
|
|
||||||
import {
|
import {
|
||||||
CommentInput,
|
CommentInput,
|
||||||
|
CommentRepliesList,
|
||||||
getMostRecentCommentableBet,
|
getMostRecentCommentableBet,
|
||||||
} from 'web/components/feed/feed-comments'
|
} from 'web/components/feed/feed-comments'
|
||||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import { groupBy } from 'lodash'
|
||||||
|
import { User } from 'common/user'
|
||||||
|
|
||||||
export function FeedAnswerCommentGroup(props: {
|
export function FeedAnswerCommentGroup(props: {
|
||||||
contract: any
|
contract: any
|
||||||
|
user: User | undefined | null
|
||||||
answer: Answer
|
answer: Answer
|
||||||
items: ActivityItem[]
|
comments: Comment[]
|
||||||
type: string
|
bets: Bet[]
|
||||||
betsByCurrentUser?: Bet[]
|
|
||||||
commentsByCurrentUser?: Comment[]
|
|
||||||
}) {
|
}) {
|
||||||
const { answer, items, contract, betsByCurrentUser, commentsByCurrentUser } =
|
const { answer, contract, comments, bets, user } = props
|
||||||
props
|
|
||||||
const { username, avatarUrl, name, text } = answer
|
const { username, avatarUrl, name, text } = answer
|
||||||
const answerElementId = `answer-${answer.id}`
|
|
||||||
const user = useUser()
|
const [replyToUsername, setReplyToUsername] = useState('')
|
||||||
const mostRecentCommentableBet = getMostRecentCommentableBet(
|
|
||||||
betsByCurrentUser ?? [],
|
|
||||||
commentsByCurrentUser ?? [],
|
|
||||||
user,
|
|
||||||
answer.number + ''
|
|
||||||
)
|
|
||||||
const prob = getDpmOutcomeProbability(contract.totalShares, answer.id)
|
|
||||||
const probPercent = formatPercent(prob)
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [showReply, setShowReply] = useState(false)
|
const [showReply, setShowReply] = useState(false)
|
||||||
const isFreeResponseContractPage = !!commentsByCurrentUser
|
|
||||||
if (mostRecentCommentableBet && !showReply) setShowReplyAndFocus(true)
|
|
||||||
const [inputRef, setInputRef] = useState<HTMLTextAreaElement | null>(null)
|
const [inputRef, setInputRef] = useState<HTMLTextAreaElement | null>(null)
|
||||||
|
const [highlighted, setHighlighted] = useState(false)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// If they've already opened the input box, focus it once again
|
const answerElementId = `answer-${answer.id}`
|
||||||
function setShowReplyAndFocus(show: boolean) {
|
const betsByUserId = groupBy(bets, (bet) => bet.userId)
|
||||||
setShowReply(show)
|
const commentsByUserId = groupBy(comments, (comment) => comment.userId)
|
||||||
|
const answerComments = comments.filter(
|
||||||
|
(comment) => comment.answerOutcome === answer.number.toString()
|
||||||
|
)
|
||||||
|
const commentReplies = comments.filter(
|
||||||
|
(comment) =>
|
||||||
|
comment.replyToCommentId &&
|
||||||
|
!comment.answerOutcome &&
|
||||||
|
answerComments.map((c) => c.id).includes(comment.replyToCommentId)
|
||||||
|
)
|
||||||
|
const commentsList = answerComments.concat(commentReplies)
|
||||||
|
|
||||||
|
const prob = getDpmOutcomeProbability(contract.totalShares, answer.id)
|
||||||
|
const probPercent = formatPercent(prob)
|
||||||
|
const betsByCurrentUser = (user && betsByUserId[user.id]) ?? []
|
||||||
|
const commentsByCurrentUser = (user && commentsByUserId[user.id]) ?? []
|
||||||
|
const isFreeResponseContractPage = !!commentsByCurrentUser
|
||||||
|
useEffect(() => {
|
||||||
|
const mostRecentCommentableBet = getMostRecentCommentableBet(
|
||||||
|
betsByCurrentUser,
|
||||||
|
commentsByCurrentUser,
|
||||||
|
user,
|
||||||
|
answer.number.toString()
|
||||||
|
)
|
||||||
|
if (mostRecentCommentableBet && !showReply)
|
||||||
|
scrollAndOpenReplyInput(undefined, answer)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [betsByCurrentUser])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Only show one comment input for a bet at a time
|
||||||
|
const usersMostRecentBet = bets
|
||||||
|
.filter((b) => b.userId === user?.id)
|
||||||
|
.sort((a, b) => b.createdTime - a.createdTime)[0]
|
||||||
|
if (
|
||||||
|
usersMostRecentBet &&
|
||||||
|
usersMostRecentBet.outcome !== answer.number.toString()
|
||||||
|
) {
|
||||||
|
setShowReply(false)
|
||||||
|
}
|
||||||
|
}, [answer.number, bets, user])
|
||||||
|
|
||||||
|
function scrollAndOpenReplyInput(comment?: Comment, answer?: Answer) {
|
||||||
|
setReplyToUsername(comment?.userUsername ?? answer?.username ?? '')
|
||||||
|
setShowReply(true)
|
||||||
inputRef?.focus()
|
inputRef?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +95,6 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
if (showReply && inputRef) inputRef.focus()
|
if (showReply && inputRef) inputRef.focus()
|
||||||
}, [inputRef, showReply])
|
}, [inputRef, showReply])
|
||||||
|
|
||||||
const [highlighted, setHighlighted] = useState(false)
|
|
||||||
const router = useRouter()
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.asPath.endsWith(`#${answerElementId}`)) {
|
if (router.asPath.endsWith(`#${answerElementId}`)) {
|
||||||
setHighlighted(true)
|
setHighlighted(true)
|
||||||
|
@ -70,7 +102,7 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
}, [answerElementId, router.asPath])
|
}, [answerElementId, router.asPath])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className={'flex-1 gap-2'}>
|
<Col className={'relative flex-1 gap-2'}>
|
||||||
<Modal open={open} setOpen={setOpen}>
|
<Modal open={open} setOpen={setOpen}>
|
||||||
<AnswerBetPanel
|
<AnswerBetPanel
|
||||||
answer={answer}
|
answer={answer}
|
||||||
|
@ -113,7 +145,7 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
className={
|
className={
|
||||||
'text-xs font-bold text-gray-500 hover:underline'
|
'text-xs font-bold text-gray-500 hover:underline'
|
||||||
}
|
}
|
||||||
onClick={() => setShowReplyAndFocus(true)}
|
onClick={() => scrollAndOpenReplyInput(undefined, answer)}
|
||||||
>
|
>
|
||||||
Reply
|
Reply
|
||||||
</button>
|
</button>
|
||||||
|
@ -143,7 +175,7 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
<div className={'justify-initial hidden sm:block'}>
|
<div className={'justify-initial hidden sm:block'}>
|
||||||
<button
|
<button
|
||||||
className={'text-xs font-bold text-gray-500 hover:underline'}
|
className={'text-xs font-bold text-gray-500 hover:underline'}
|
||||||
onClick={() => setShowReplyAndFocus(true)}
|
onClick={() => scrollAndOpenReplyInput(undefined, answer)}
|
||||||
>
|
>
|
||||||
Reply
|
Reply
|
||||||
</button>
|
</button>
|
||||||
|
@ -151,36 +183,31 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<CommentRepliesList
|
||||||
{items.map((item, index) => (
|
contract={contract}
|
||||||
<div
|
commentsList={commentsList}
|
||||||
key={item.id}
|
betsByUserId={betsByUserId}
|
||||||
className={clsx(
|
smallAvatar={true}
|
||||||
'relative ml-8',
|
truncate={false}
|
||||||
index !== items.length - 1 && 'pb-4'
|
bets={bets}
|
||||||
)}
|
scrollAndOpenReplyInput={scrollAndOpenReplyInput}
|
||||||
>
|
treatFirstIndexEqually={true}
|
||||||
{index !== items.length - 1 ? (
|
/>
|
||||||
<span
|
|
||||||
className="absolute top-5 left-5 -ml-px h-[calc(100%-1rem)] w-0.5 bg-gray-200"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<div className="relative flex items-start space-x-3">
|
|
||||||
<FeedItem item={item} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{showReply && (
|
{showReply && (
|
||||||
<div className={'ml-8 pt-4'}>
|
<div className={'ml-6 pt-4'}>
|
||||||
|
<span
|
||||||
|
className="absolute -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<CommentInput
|
<CommentInput
|
||||||
contract={contract}
|
contract={contract}
|
||||||
betsByCurrentUser={betsByCurrentUser ?? []}
|
betsByCurrentUser={betsByCurrentUser}
|
||||||
commentsByCurrentUser={commentsByCurrentUser ?? []}
|
commentsByCurrentUser={commentsByCurrentUser}
|
||||||
answerOutcome={answer.number + ''}
|
parentAnswerOutcome={answer.number.toString()}
|
||||||
replyToUsername={answer.username}
|
replyToUsername={replyToUsername}
|
||||||
setRef={setInputRef}
|
setRef={setInputRef}
|
||||||
|
onSubmitComment={() => setShowReply(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Comment } from 'common/comment'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { minBy, maxBy, groupBy, partition, sumBy } from 'lodash'
|
import { minBy, maxBy, groupBy, partition, sumBy, Dictionary } from 'lodash'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
@ -54,15 +54,77 @@ export function FeedCommentThread(props: {
|
||||||
if (showReply && inputRef) inputRef.focus()
|
if (showReply && inputRef) inputRef.focus()
|
||||||
}, [inputRef, showReply])
|
}, [inputRef, showReply])
|
||||||
return (
|
return (
|
||||||
<div className={'flex-col pr-1'}>
|
<div className={'w-full flex-col pr-1'}>
|
||||||
|
<span
|
||||||
|
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<CommentRepliesList
|
||||||
|
contract={contract}
|
||||||
|
commentsList={commentsList}
|
||||||
|
betsByUserId={betsByUserId}
|
||||||
|
smallAvatar={smallAvatar}
|
||||||
|
truncate={truncate}
|
||||||
|
bets={bets}
|
||||||
|
scrollAndOpenReplyInput={scrollAndOpenReplyInput}
|
||||||
|
/>
|
||||||
|
{showReply && (
|
||||||
|
<div className={'-pb-2 ml-6 flex flex-col pt-5'}>
|
||||||
|
<span
|
||||||
|
className="absolute -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<CommentInput
|
||||||
|
contract={contract}
|
||||||
|
betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
|
||||||
|
commentsByCurrentUser={comments.filter(
|
||||||
|
(c) => c.userId === user?.id
|
||||||
|
)}
|
||||||
|
parentCommentId={parentComment.id}
|
||||||
|
replyToUsername={replyToUsername}
|
||||||
|
parentAnswerOutcome={comments[0].answerOutcome}
|
||||||
|
setRef={setInputRef}
|
||||||
|
onSubmitComment={() => setShowReply(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CommentRepliesList(props: {
|
||||||
|
contract: Contract
|
||||||
|
commentsList: Comment[]
|
||||||
|
betsByUserId: Dictionary<Bet[]>
|
||||||
|
scrollAndOpenReplyInput: (comment: Comment) => void
|
||||||
|
bets: Bet[]
|
||||||
|
treatFirstIndexEqually?: boolean
|
||||||
|
smallAvatar?: boolean
|
||||||
|
truncate?: boolean
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
contract,
|
||||||
|
commentsList,
|
||||||
|
betsByUserId,
|
||||||
|
truncate,
|
||||||
|
smallAvatar,
|
||||||
|
bets,
|
||||||
|
scrollAndOpenReplyInput,
|
||||||
|
treatFirstIndexEqually,
|
||||||
|
} = props
|
||||||
|
return (
|
||||||
|
<>
|
||||||
{commentsList.map((comment, commentIdx) => (
|
{commentsList.map((comment, commentIdx) => (
|
||||||
<div
|
<div
|
||||||
key={comment.id}
|
key={comment.id}
|
||||||
id={comment.id}
|
id={comment.id}
|
||||||
className={clsx('relative', commentIdx === 0 ? '' : 'mt-3 ml-6')}
|
className={clsx(
|
||||||
|
'relative',
|
||||||
|
!treatFirstIndexEqually && commentIdx === 0 ? '' : 'mt-3 ml-6'
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{/*draw a gray line from the comment to the left:*/}
|
{/*draw a gray line from the comment to the left:*/}
|
||||||
{commentIdx != 0 && (
|
{(treatFirstIndexEqually || commentIdx != 0) && (
|
||||||
<span
|
<span
|
||||||
className="absolute -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
className="absolute -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
@ -87,24 +149,7 @@ export function FeedCommentThread(props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{showReply && (
|
</>
|
||||||
<div className={'-pb-2 ml-6 flex flex-col pt-5'}>
|
|
||||||
<span
|
|
||||||
className="absolute -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<CommentInput
|
|
||||||
contract={contract}
|
|
||||||
betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
|
|
||||||
commentsByCurrentUser={comments}
|
|
||||||
parentComment={parentComment}
|
|
||||||
replyToUsername={replyToUsername}
|
|
||||||
answerOutcome={comments[0].answerOutcome}
|
|
||||||
setRef={setInputRef}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +274,13 @@ export function getMostRecentCommentableBet(
|
||||||
user?: User | null,
|
user?: User | null,
|
||||||
answerOutcome?: string
|
answerOutcome?: string
|
||||||
) {
|
) {
|
||||||
return betsByCurrentUser
|
let sortedBetsByCurrentUser = betsByCurrentUser.sort(
|
||||||
|
(a, b) => b.createdTime - a.createdTime
|
||||||
|
)
|
||||||
|
if (answerOutcome) {
|
||||||
|
sortedBetsByCurrentUser = sortedBetsByCurrentUser.slice(0, 1)
|
||||||
|
}
|
||||||
|
return sortedBetsByCurrentUser
|
||||||
.filter((bet) => {
|
.filter((bet) => {
|
||||||
if (
|
if (
|
||||||
canCommentOnBet(bet, user) &&
|
canCommentOnBet(bet, user) &&
|
||||||
|
@ -238,12 +289,10 @@ export function getMostRecentCommentableBet(
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (!answerOutcome) return true
|
if (!answerOutcome) return true
|
||||||
// If we're in free response, don't allow commenting on ante bet
|
|
||||||
return answerOutcome === bet.outcome
|
return answerOutcome === bet.outcome
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
.sort((b1, b2) => b1.createdTime - b2.createdTime)
|
|
||||||
.pop()
|
.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,20 +315,22 @@ export function CommentInput(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
betsByCurrentUser: Bet[]
|
betsByCurrentUser: Bet[]
|
||||||
commentsByCurrentUser: Comment[]
|
commentsByCurrentUser: Comment[]
|
||||||
// Tie a comment to an free response answer outcome
|
|
||||||
answerOutcome?: string
|
|
||||||
// Tie a comment to another comment
|
|
||||||
parentComment?: Comment
|
|
||||||
replyToUsername?: string
|
replyToUsername?: string
|
||||||
setRef?: (ref: HTMLTextAreaElement) => void
|
setRef?: (ref: HTMLTextAreaElement) => void
|
||||||
|
// Reply to a free response answer
|
||||||
|
parentAnswerOutcome?: string
|
||||||
|
// Reply to another comment
|
||||||
|
parentCommentId?: string
|
||||||
|
onSubmitComment?: () => void
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
contract,
|
contract,
|
||||||
betsByCurrentUser,
|
betsByCurrentUser,
|
||||||
commentsByCurrentUser,
|
commentsByCurrentUser,
|
||||||
answerOutcome,
|
parentAnswerOutcome,
|
||||||
parentComment,
|
parentCommentId,
|
||||||
replyToUsername,
|
replyToUsername,
|
||||||
|
onSubmitComment,
|
||||||
setRef,
|
setRef,
|
||||||
} = props
|
} = props
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
@ -291,7 +342,7 @@ export function CommentInput(props: {
|
||||||
betsByCurrentUser,
|
betsByCurrentUser,
|
||||||
commentsByCurrentUser,
|
commentsByCurrentUser,
|
||||||
user,
|
user,
|
||||||
answerOutcome
|
parentAnswerOutcome
|
||||||
)
|
)
|
||||||
const { id } = mostRecentCommentableBet || { id: undefined }
|
const { id } = mostRecentCommentableBet || { id: undefined }
|
||||||
|
|
||||||
|
@ -312,9 +363,10 @@ export function CommentInput(props: {
|
||||||
comment,
|
comment,
|
||||||
user,
|
user,
|
||||||
betId,
|
betId,
|
||||||
answerOutcome,
|
parentAnswerOutcome,
|
||||||
parentComment?.id
|
parentCommentId
|
||||||
)
|
)
|
||||||
|
onSubmitComment?.()
|
||||||
setComment('')
|
setComment('')
|
||||||
setFocused(false)
|
setFocused(false)
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
|
@ -326,7 +378,7 @@ export function CommentInput(props: {
|
||||||
betsByCurrentUser
|
betsByCurrentUser
|
||||||
)
|
)
|
||||||
|
|
||||||
const shouldCollapseAfterClickOutside = false
|
const shouldCollapseAfterClickOutside = !comment
|
||||||
|
|
||||||
const isNumeric = contract.outcomeType === 'NUMERIC'
|
const isNumeric = contract.outcomeType === 'NUMERIC'
|
||||||
|
|
||||||
|
@ -373,74 +425,45 @@ export function CommentInput(props: {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Row className="grid grid-cols-8 gap-1.5 text-gray-700">
|
<Row className="gap-1.5 text-gray-700">
|
||||||
<Col
|
<Textarea
|
||||||
|
ref={setRef}
|
||||||
|
value={comment}
|
||||||
|
onChange={(e) => setComment(e.target.value)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'col-span-8 sm:col-span-6',
|
'textarea textarea-bordered w-full resize-none'
|
||||||
!user && 'col-span-8'
|
|
||||||
)}
|
)}
|
||||||
>
|
placeholder={
|
||||||
<Textarea
|
parentCommentId || parentAnswerOutcome
|
||||||
ref={setRef}
|
? 'Write a reply... '
|
||||||
value={comment}
|
: 'Write a comment...'
|
||||||
onChange={(e) => setComment(e.target.value)}
|
}
|
||||||
className={clsx('textarea textarea-bordered resize-none')}
|
autoFocus={true}
|
||||||
placeholder={
|
onFocus={() => setFocused(true)}
|
||||||
parentComment || answerOutcome
|
onBlur={() =>
|
||||||
? 'Write a reply... '
|
shouldCollapseAfterClickOutside && setFocused(false)
|
||||||
: 'Write a comment...'
|
}
|
||||||
|
maxLength={MAX_COMMENT_LENGTH}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault()
|
||||||
|
submitComment(id)
|
||||||
|
e.currentTarget.blur()
|
||||||
}
|
}
|
||||||
autoFocus={focused}
|
}}
|
||||||
rows={focused ? 3 : 1}
|
/>
|
||||||
onFocus={() => setFocused(true)}
|
|
||||||
onBlur={() =>
|
|
||||||
shouldCollapseAfterClickOutside && setFocused(false)
|
|
||||||
}
|
|
||||||
maxLength={MAX_COMMENT_LENGTH}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault()
|
|
||||||
submitComment(id)
|
|
||||||
e.currentTarget.blur()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
{!user && (
|
|
||||||
<Col
|
|
||||||
className={clsx(
|
|
||||||
'col-span-8 sm:col-span-2',
|
|
||||||
focused ? 'justify-end' : 'justify-center'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
'btn btn-outline btn-sm text-transform: capitalize'
|
|
||||||
}
|
|
||||||
onClick={() => submitComment(id)}
|
|
||||||
>
|
|
||||||
Sign in to Comment
|
|
||||||
</button>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Col
|
<Col className={clsx(focused ? 'justify-end' : 'justify-center')}>
|
||||||
className={clsx(
|
|
||||||
'col-span-1 sm:col-span-2',
|
|
||||||
focused ? 'justify-end' : 'justify-center'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{user && !isSubmitting && (
|
{user && !isSubmitting && (
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'btn btn-ghost btn-sm block flex flex-row capitalize',
|
'btn btn-ghost btn-sm absolute right-2 block flex flex-row capitalize',
|
||||||
'absolute bottom-4 right-1 col-span-1',
|
parentCommentId || parentAnswerOutcome
|
||||||
parentComment ? ' bottom-6 right-2.5' : '',
|
? ' bottom-4'
|
||||||
'sm:relative sm:bottom-0 sm:right-0 sm:col-span-2',
|
: ' bottom-2',
|
||||||
focused && comment
|
(!focused || !comment) &&
|
||||||
? 'sm:btn-outline'
|
'pointer-events-none text-gray-500'
|
||||||
: 'pointer-events-none text-gray-500'
|
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!focused) return
|
if (!focused) return
|
||||||
|
@ -449,12 +472,9 @@ export function CommentInput(props: {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className={'hidden sm:block'}>
|
|
||||||
{parentComment || answerOutcome ? 'Reply' : 'Comment'}
|
|
||||||
</span>
|
|
||||||
{focused && (
|
{focused && (
|
||||||
<PaperAirplaneIcon
|
<PaperAirplaneIcon
|
||||||
className={'m-0 min-w-[22px] rotate-90 p-0 sm:hidden'}
|
className={'m-0 min-w-[22px] rotate-90 p-0 '}
|
||||||
height={25}
|
height={25}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -465,6 +485,16 @@ export function CommentInput(props: {
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
{!user && (
|
||||||
|
<button
|
||||||
|
className={'btn btn-outline btn-sm mt-2 normal-case'}
|
||||||
|
onClick={() => submitComment(id)}
|
||||||
|
>
|
||||||
|
Sign in to comment
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user