From e1d4600e48354d28ee3a00b1e31f051d13963c2b Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Tue, 3 May 2022 10:47:28 -0400 Subject: [PATCH] Add General Comments section to FR answers --- web/components/feed/activity-items.ts | 75 ++++++++++++++++--------- web/components/feed/feed-items.tsx | 81 ++++++++++++++++++++++----- 2 files changed, 115 insertions(+), 41 deletions(-) diff --git a/web/components/feed/activity-items.ts b/web/components/feed/activity-items.ts index 5bcdbfdc..7b37f1ee 100644 --- a/web/components/feed/activity-items.ts +++ b/web/components/feed/activity-items.ts @@ -1,6 +1,6 @@ import _ from 'lodash' -import { Answer } from '../../../common/answer' +import { Answer, getNoneAnswer } from '../../../common/answer' import { Bet } from '../../../common/bet' import { getOutcomeProbability } from '../../../common/calculate' import { Comment } from '../../../common/comment' @@ -23,6 +23,7 @@ export type ActivityItem = | CloseItem | ResolveItem | CommentInputItem + | GeneralCommentsItem type BaseActivityItem = { id: string @@ -75,6 +76,11 @@ export type AnswerGroupItem = BaseActivityItem & { items: ActivityItem[] } +export type GeneralCommentsItem = BaseActivityItem & { + type: 'generalcomments' + items: ActivityItem[] +} + export type CloseItem = BaseActivityItem & { type: 'close' } @@ -83,6 +89,7 @@ export type ResolveItem = BaseActivityItem & { type: 'resolve' } +export const GENERAL_COMMENTS_OUTCOME_ID = 'General Comments' const DAY_IN_MS = 24 * 60 * 60 * 1000 const ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW = 3 @@ -276,37 +283,42 @@ function getAnswerAndCommentInputGroups( outcomes = _.sortBy(outcomes, (outcome) => getOutcomeProbability(contract, outcome) ) + + function collateCommentsSectionForOutcome(outcome: string) { + const answerBets = bets.filter((bet) => bet.outcome === outcome) + const answerComments = comments.filter( + (comment) => + comment.answerOutcome === outcome || + answerBets.some((bet) => bet.id === comment.betId) + ) + let items = [] + items.push({ + type: 'commentInput' as const, + id: 'commentInputFor' + outcome, + contract, + betsByCurrentUser: user + ? bets.filter((bet) => bet.userId === user.id) + : [], + comments: comments, + answerOutcome: outcome, + }) + items.push( + ...getCommentsWithPositions( + answerBets, + answerComments, + contract + ).reverse() + ) + return items + } + const answerGroups = outcomes .map((outcome) => { - const answerBets = bets.filter((bet) => bet.outcome === outcome) - const answerComments = comments.filter((comment) => - answerBets.some( - (bet) => bet.id === comment.betId || comment.answerOutcome === outcome - ) - ) const answer = contract.answers?.find( (answer) => answer.id === outcome ) as Answer - // Create a list of items for each answer group made up of bets with comments and a comment input - let items = [] - items.push({ - type: 'commentInput' as const, - id: 'commentInputFor' + outcome, - contract, - betsByCurrentUser: user - ? bets.filter((bet) => bet.userId === user.id) - : [], - comments: comments, - answerOutcome: outcome, - }) - items.push( - ...getCommentsWithPositions( - answerBets, - answerComments, - contract - ).reverse() - ) + const items = collateCommentsSectionForOutcome(outcome) return { id: outcome, @@ -317,7 +329,16 @@ function getAnswerAndCommentInputGroups( user, } }) - .filter((group) => group.answer) + .filter((group) => group.answer) as ActivityItem[] + + const outcome = GENERAL_COMMENTS_OUTCOME_ID + const items = collateCommentsSectionForOutcome(outcome) + answerGroups.unshift({ + id: outcome, + type: 'generalcomments' as const, + contract, + items, + }) return answerGroups } diff --git a/web/components/feed/feed-items.tsx b/web/components/feed/feed-items.tsx index 10fab246..6e7ab8f8 100644 --- a/web/components/feed/feed-items.tsx +++ b/web/components/feed/feed-items.tsx @@ -37,7 +37,7 @@ import { fromNow } from '../../lib/util/time' import BetRow from '../bet-row' import { Avatar } from '../avatar' import { Answer } from '../../../common/answer' -import { ActivityItem } from './activity-items' +import { ActivityItem, GENERAL_COMMENTS_OUTCOME_ID } from './activity-items' import { Binary, CPMM, @@ -123,6 +123,8 @@ function FeedItem(props: { item: ActivityItem }) { return case 'commentInput': return + case 'generalcomments': + return } } @@ -222,30 +224,46 @@ export function CommentInput(props: { contract: Contract betsByCurrentUser: Bet[] comments: Comment[] + // Only for free response comment inputs answerOutcome?: string }) { const { contract, betsByCurrentUser, comments, answerOutcome } = props const user = useUser() const [comment, setComment] = useState('') + const [focused, setFocused] = useState(false) + + function setFocusedIfFreeResponse(focus: boolean) { + if (answerOutcome != undefined) setFocused(focus) + } // Should this be oldest bet or most recent bet? const mostRecentCommentableBet = betsByCurrentUser - .filter( - (bet) => - canCommentOnBet(bet, bet.createdTime, user) && + .filter((bet) => { + if ( + canCommentOnBet(bet, user) && + // The bet doesn't already have a comment !comments.some((comment) => comment.betId == bet.id) - ) + ) { + if (!answerOutcome) return true + // If we're in free response, don't allow commenting on ante bet + return ( + bet.outcome !== GENERAL_COMMENTS_OUTCOME_ID && + answerOutcome === bet.outcome + ) + } + return false + }) .sort((b1, b2) => b1.createdTime - b2.createdTime) .pop() const { id } = mostRecentCommentableBet || { id: undefined } - async function submitComment(id: string | undefined) { + async function submitComment(betId: string | undefined) { if (!comment) return if (!user) { return await firebaseLogin() } - await createComment(contract.id, comment, user, id) + await createComment(contract.id, comment, user, betId, answerOutcome) setComment('') } @@ -286,7 +304,9 @@ export function CommentInput(props: { onChange={(e) => setComment(e.target.value)} className="textarea textarea-bordered w-full resize-none" placeholder="Add a comment..." - rows={3} + rows={answerOutcome == undefined || focused ? 3 : 1} + onFocus={() => setFocusedIfFreeResponse(true)} + onBlur={() => !comment && setFocusedIfFreeResponse(false)} maxLength={MAX_COMMENT_LENGTH} onKeyDown={(e) => { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { @@ -298,7 +318,10 @@ export function CommentInput(props: { className={ 'btn btn-outline btn-sm text-transform: mt-1 capitalize' } - onClick={() => submitComment(id)} + onClick={() => { + submitComment(id) + setFocusedIfFreeResponse(false) + }} > {user ? 'Comment' : 'Sign in to comment'} @@ -561,12 +584,11 @@ export function FeedQuestion(props: { ) } -function canCommentOnBet(bet: Bet, createdTime: number, user?: User | null) { - const isSelf = user?.id === bet.userId +function canCommentOnBet(bet: Bet, user?: User | null) { + const { userId, createdTime, isRedemption } = bet + const isSelf = user?.id === userId // You can comment if your bet was posted in the last hour - return ( - !bet.isRedemption && isSelf && Date.now() - createdTime < 60 * 60 * 1000 - ) + return !isRedemption && isSelf && Date.now() - createdTime < 60 * 60 * 1000 } function FeedDescription(props: { contract: Contract }) { @@ -835,6 +857,37 @@ function FeedAnswerGroup(props: { ) } +function FeedGeneralComments(props: { + contract: FullContract + items: ActivityItem[] + type: string +}) { + const { items } = props + + return ( + +
General Comments
+
+ {items.map((item, index) => ( +
+ {index !== items.length - 1 ? ( +
+ ))} + + ) +} + // TODO: Should highlight the entire Feed segment function FeedExpand(props: { setExpanded: (expanded: boolean) => void }) { const { setExpanded } = props