Uncoupled CommentInput from Contracts
This commit is contained in:
parent
048de52bee
commit
60d207aa6a
175
web/components/comment-input.tsx
Normal file
175
web/components/comment-input.tsx
Normal file
|
@ -0,0 +1,175 @@
|
|||
import { PaperAirplaneIcon } from '@heroicons/react/solid'
|
||||
import { Editor } from '@tiptap/react'
|
||||
import clsx from 'clsx'
|
||||
import { User } from 'common/user'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||
import { MAX_COMMENT_LENGTH } from 'web/lib/firebase/comments'
|
||||
import { Avatar } from './avatar'
|
||||
import { TextEditor, useTextEditor } from './editor'
|
||||
import { Row } from './layout/row'
|
||||
import { LoadingIndicator } from './loading-indicator'
|
||||
|
||||
export function CommentInput(props: {
|
||||
replyToUser?: { id: string; username: string }
|
||||
// Reply to a free response answer
|
||||
parentAnswerOutcome?: string
|
||||
// Reply to another comment
|
||||
parentCommentId?: string
|
||||
onSubmitComment?: (editor: Editor, betId: string | undefined) => void
|
||||
className?: string
|
||||
presetId?: string
|
||||
}) {
|
||||
const {
|
||||
parentAnswerOutcome,
|
||||
parentCommentId,
|
||||
replyToUser,
|
||||
onSubmitComment,
|
||||
presetId,
|
||||
} = props
|
||||
const user = useUser()
|
||||
|
||||
const { editor, upload } = useTextEditor({
|
||||
simple: true,
|
||||
max: MAX_COMMENT_LENGTH,
|
||||
placeholder:
|
||||
!!parentCommentId || !!parentAnswerOutcome
|
||||
? 'Write a reply...'
|
||||
: 'Write a comment...',
|
||||
})
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
async function submitComment(betId: string | undefined) {
|
||||
if (!editor || editor.isEmpty || isSubmitting) return
|
||||
setIsSubmitting(true)
|
||||
onSubmitComment?.(editor, betId)
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
if (user?.isBannedFromPosting) return <></>
|
||||
|
||||
return (
|
||||
<Row className={clsx(props.className, 'mb-2 gap-1 sm:gap-2')}>
|
||||
<Avatar
|
||||
avatarUrl={user?.avatarUrl}
|
||||
username={user?.username}
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
/>
|
||||
<div className="min-w-0 flex-1 pl-0.5 text-sm">
|
||||
<CommentInputTextArea
|
||||
editor={editor}
|
||||
upload={upload}
|
||||
replyToUser={replyToUser}
|
||||
user={user}
|
||||
submitComment={submitComment}
|
||||
isSubmitting={isSubmitting}
|
||||
presetId={presetId}
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export function CommentInputTextArea(props: {
|
||||
user: User | undefined | null
|
||||
replyToUser?: { id: string; username: string }
|
||||
editor: Editor | null
|
||||
upload: Parameters<typeof TextEditor>[0]['upload']
|
||||
submitComment: (id?: string) => void
|
||||
isSubmitting: boolean
|
||||
submitOnEnter?: boolean
|
||||
presetId?: string
|
||||
}) {
|
||||
const {
|
||||
user,
|
||||
editor,
|
||||
upload,
|
||||
submitComment,
|
||||
presetId,
|
||||
isSubmitting,
|
||||
submitOnEnter,
|
||||
replyToUser,
|
||||
} = props
|
||||
const isMobile = (useWindowSize().width ?? 0) < 768 // TODO: base off input device (keybord vs touch)
|
||||
|
||||
useEffect(() => {
|
||||
editor?.setEditable(!isSubmitting)
|
||||
}, [isSubmitting, editor])
|
||||
|
||||
const submit = () => {
|
||||
submitComment(presetId)
|
||||
editor?.commands?.clearContent()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
// submit on Enter key
|
||||
editor.setOptions({
|
||||
editorProps: {
|
||||
handleKeyDown: (view, event) => {
|
||||
if (
|
||||
submitOnEnter &&
|
||||
event.key === 'Enter' &&
|
||||
!event.shiftKey &&
|
||||
(!isMobile || event.ctrlKey || event.metaKey) &&
|
||||
// mention list is closed
|
||||
!(view.state as any).mention$.active
|
||||
) {
|
||||
submit()
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
})
|
||||
// insert at mention and focus
|
||||
if (replyToUser) {
|
||||
editor
|
||||
.chain()
|
||||
.setContent({
|
||||
type: 'mention',
|
||||
attrs: { label: replyToUser.username, id: replyToUser.id },
|
||||
})
|
||||
.insertContent(' ')
|
||||
.focus()
|
||||
.run()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editor])
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextEditor editor={editor} upload={upload}>
|
||||
{user && !isSubmitting && (
|
||||
<button
|
||||
className="btn btn-ghost btn-sm px-2 disabled:bg-inherit disabled:text-gray-300"
|
||||
disabled={!editor || editor.isEmpty}
|
||||
onClick={submit}
|
||||
>
|
||||
<PaperAirplaneIcon className="m-0 h-[25px] min-w-[22px] rotate-90 p-0" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isSubmitting && (
|
||||
<LoadingIndicator spinnerClassName={'border-gray-500'} />
|
||||
)}
|
||||
</TextEditor>
|
||||
<Row>
|
||||
{!user && (
|
||||
<button
|
||||
className={'btn btn-outline btn-sm mt-2 normal-case'}
|
||||
onClick={() => submitComment(presetId)}
|
||||
>
|
||||
Add my comment
|
||||
</button>
|
||||
)}
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -6,7 +6,7 @@ import { getOutcomeProbability } from 'common/calculate'
|
|||
import { FeedBet } from './feed-bets'
|
||||
import { FeedLiquidity } from './feed-liquidity'
|
||||
import { FeedAnswerCommentGroup } from './feed-answer-comment-group'
|
||||
import { FeedCommentThread, CommentInput } from './feed-comments'
|
||||
import { FeedCommentThread, ContractCommentInput } from './feed-comments'
|
||||
import { User } from 'common/user'
|
||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||
import { LiquidityProvision } from 'common/liquidity-provision'
|
||||
|
@ -72,7 +72,7 @@ export function ContractCommentsActivity(props: {
|
|||
|
||||
return (
|
||||
<>
|
||||
<CommentInput
|
||||
<ContractCommentInput
|
||||
className="mb-5"
|
||||
contract={contract}
|
||||
betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Avatar } from 'web/components/avatar'
|
|||
import { Linkify } from 'web/components/linkify'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
CommentInput,
|
||||
ContractCommentInput,
|
||||
FeedComment,
|
||||
getMostRecentCommentableBet,
|
||||
} from 'web/components/feed/feed-comments'
|
||||
|
@ -177,7 +177,7 @@ export function FeedAnswerCommentGroup(props: {
|
|||
className="absolute -left-1 -ml-[1px] mt-[1.25rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CommentInput
|
||||
<ContractCommentInput
|
||||
contract={contract}
|
||||
betsByCurrentUser={betsByCurrentUser}
|
||||
commentsByCurrentUser={commentsByCurrentUser}
|
||||
|
|
|
@ -13,22 +13,18 @@ import { Avatar } from 'web/components/avatar'
|
|||
import { OutcomeLabel } from 'web/components/outcome-label'
|
||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||
import {
|
||||
createCommentOnContract,
|
||||
MAX_COMMENT_LENGTH,
|
||||
} from 'web/lib/firebase/comments'
|
||||
import { createCommentOnContract } from 'web/lib/firebase/comments'
|
||||
import { BetStatusText } from 'web/components/feed/feed-bets'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { getProbability } from 'common/calculate'
|
||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||
import { PaperAirplaneIcon } from '@heroicons/react/outline'
|
||||
import { track } from 'web/lib/service/analytics'
|
||||
import { Tipper } from '../tipper'
|
||||
import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns'
|
||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||
import { Content, TextEditor, useTextEditor } from '../editor'
|
||||
|
||||
import { Content } from '../editor'
|
||||
import { Editor } from '@tiptap/react'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import { CommentInput } from '../comment-input'
|
||||
|
||||
export function FeedCommentThread(props: {
|
||||
user: User | null | undefined
|
||||
|
@ -90,14 +86,16 @@ export function FeedCommentThread(props: {
|
|||
className="absolute -left-1 -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CommentInput
|
||||
<ContractCommentInput
|
||||
contract={contract}
|
||||
betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
|
||||
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
||||
parentCommentId={parentComment.id}
|
||||
replyToUser={replyTo}
|
||||
parentAnswerOutcome={parentComment.answerOutcome}
|
||||
onSubmitComment={() => setShowReply(false)}
|
||||
onSubmitComment={() => {
|
||||
setShowReply(false)
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
|
@ -271,67 +269,76 @@ function CommentStatus(props: {
|
|||
)
|
||||
}
|
||||
|
||||
//TODO: move commentinput and comment input text area into their own files
|
||||
export function CommentInput(props: {
|
||||
export function ContractCommentInput(props: {
|
||||
contract: Contract
|
||||
betsByCurrentUser: Bet[]
|
||||
commentsByCurrentUser: ContractComment[]
|
||||
className?: string
|
||||
parentAnswerOutcome?: string | undefined
|
||||
replyToUser?: { id: string; username: string }
|
||||
// Reply to a free response answer
|
||||
parentAnswerOutcome?: string
|
||||
// Reply to another comment
|
||||
parentCommentId?: string
|
||||
onSubmitComment?: () => void
|
||||
}) {
|
||||
const {
|
||||
contract,
|
||||
betsByCurrentUser,
|
||||
commentsByCurrentUser,
|
||||
className,
|
||||
parentAnswerOutcome,
|
||||
parentCommentId,
|
||||
replyToUser,
|
||||
onSubmitComment,
|
||||
} = props
|
||||
const user = useUser()
|
||||
const { editor, upload } = useTextEditor({
|
||||
simple: true,
|
||||
max: MAX_COMMENT_LENGTH,
|
||||
placeholder:
|
||||
!!parentCommentId || !!parentAnswerOutcome
|
||||
? 'Write a reply...'
|
||||
: 'Write a comment...',
|
||||
})
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const mostRecentCommentableBet = getMostRecentCommentableBet(
|
||||
betsByCurrentUser,
|
||||
commentsByCurrentUser,
|
||||
user,
|
||||
parentAnswerOutcome
|
||||
)
|
||||
const { id } = mostRecentCommentableBet || { id: undefined }
|
||||
|
||||
async function submitComment(betId: string | undefined) {
|
||||
async function onSubmitComment(editor: Editor, betId: string | undefined) {
|
||||
if (!user) {
|
||||
track('sign in to comment')
|
||||
return await firebaseLogin()
|
||||
}
|
||||
if (!editor || editor.isEmpty || isSubmitting) return
|
||||
setIsSubmitting(true)
|
||||
await createCommentOnContract(
|
||||
contract.id,
|
||||
props.contract.id,
|
||||
editor.getJSON(),
|
||||
user,
|
||||
betId,
|
||||
parentAnswerOutcome,
|
||||
parentCommentId
|
||||
props.parentAnswerOutcome,
|
||||
props.parentCommentId
|
||||
)
|
||||
onSubmitComment?.()
|
||||
setIsSubmitting(false)
|
||||
props.onSubmitComment?.()
|
||||
}
|
||||
|
||||
const mostRecentCommentableBet = getMostRecentCommentableBet(
|
||||
props.betsByCurrentUser,
|
||||
props.commentsByCurrentUser,
|
||||
user,
|
||||
props.parentAnswerOutcome
|
||||
)
|
||||
|
||||
const { id } = mostRecentCommentableBet || { id: undefined }
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<CommentBetArea
|
||||
betsByCurrentUser={props.betsByCurrentUser}
|
||||
contract={props.contract}
|
||||
commentsByCurrentUser={props.commentsByCurrentUser}
|
||||
parentAnswerOutcome={props.parentAnswerOutcome}
|
||||
user={useUser()}
|
||||
className={props.className}
|
||||
mostRecentCommentableBet={mostRecentCommentableBet}
|
||||
/>
|
||||
<CommentInput
|
||||
replyToUser={props.replyToUser}
|
||||
parentAnswerOutcome={props.parentAnswerOutcome}
|
||||
parentCommentId={props.parentCommentId}
|
||||
onSubmitComment={onSubmitComment}
|
||||
className={props.className}
|
||||
presetId={id}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function CommentBetArea(props: {
|
||||
betsByCurrentUser: Bet[]
|
||||
contract: Contract
|
||||
commentsByCurrentUser: ContractComment[]
|
||||
parentAnswerOutcome?: string
|
||||
user?: User | null
|
||||
className?: string
|
||||
mostRecentCommentableBet?: Bet
|
||||
}) {
|
||||
const { betsByCurrentUser, contract, user, mostRecentCommentableBet } = props
|
||||
|
||||
const { userPosition, outcome } = getBettorsLargestPositionBeforeTime(
|
||||
contract,
|
||||
Date.now(),
|
||||
|
@ -340,158 +347,36 @@ export function CommentInput(props: {
|
|||
|
||||
const isNumeric = contract.outcomeType === 'NUMERIC'
|
||||
|
||||
if (user?.isBannedFromPosting) return <></>
|
||||
|
||||
return (
|
||||
<Row className={clsx(className, 'mb-2 gap-1 sm:gap-2')}>
|
||||
<Avatar
|
||||
avatarUrl={user?.avatarUrl}
|
||||
username={user?.username}
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
/>
|
||||
<div className="min-w-0 flex-1 pl-0.5 text-sm">
|
||||
<div className="mb-1 text-gray-500">
|
||||
{mostRecentCommentableBet && (
|
||||
<BetStatusText
|
||||
<Row className={clsx(props.className, 'mb-2 gap-1 sm:gap-2')}>
|
||||
<div className="mb-1 text-gray-500">
|
||||
{mostRecentCommentableBet && (
|
||||
<BetStatusText
|
||||
contract={contract}
|
||||
bet={mostRecentCommentableBet}
|
||||
isSelf={true}
|
||||
hideOutcome={isNumeric || contract.outcomeType === 'FREE_RESPONSE'}
|
||||
/>
|
||||
)}
|
||||
{!mostRecentCommentableBet && user && userPosition > 0 && !isNumeric && (
|
||||
<>
|
||||
{"You're"}
|
||||
<CommentStatus
|
||||
outcome={outcome}
|
||||
contract={contract}
|
||||
bet={mostRecentCommentableBet}
|
||||
isSelf={true}
|
||||
hideOutcome={
|
||||
isNumeric || contract.outcomeType === 'FREE_RESPONSE'
|
||||
prob={
|
||||
contract.outcomeType === 'BINARY'
|
||||
? getProbability(contract)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!mostRecentCommentableBet && user && userPosition > 0 && !isNumeric && (
|
||||
<>
|
||||
{"You're"}
|
||||
<CommentStatus
|
||||
outcome={outcome}
|
||||
contract={contract}
|
||||
prob={
|
||||
contract.outcomeType === 'BINARY'
|
||||
? getProbability(contract)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<CommentInputTextArea
|
||||
editor={editor}
|
||||
upload={upload}
|
||||
replyToUser={replyToUser}
|
||||
user={user}
|
||||
submitComment={submitComment}
|
||||
isSubmitting={isSubmitting}
|
||||
presetId={id}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export function CommentInputTextArea(props: {
|
||||
user: User | undefined | null
|
||||
replyToUser?: { id: string; username: string }
|
||||
editor: Editor | null
|
||||
upload: Parameters<typeof TextEditor>[0]['upload']
|
||||
submitComment: (id?: string) => void
|
||||
isSubmitting: boolean
|
||||
submitOnEnter?: boolean
|
||||
presetId?: string
|
||||
}) {
|
||||
const {
|
||||
user,
|
||||
editor,
|
||||
upload,
|
||||
submitComment,
|
||||
presetId,
|
||||
isSubmitting,
|
||||
submitOnEnter,
|
||||
replyToUser,
|
||||
} = props
|
||||
const isMobile = (useWindowSize().width ?? 0) < 768 // TODO: base off input device (keybord vs touch)
|
||||
|
||||
useEffect(() => {
|
||||
editor?.setEditable(!isSubmitting)
|
||||
}, [isSubmitting, editor])
|
||||
|
||||
const submit = () => {
|
||||
submitComment(presetId)
|
||||
editor?.commands?.clearContent()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
// submit on Enter key
|
||||
editor.setOptions({
|
||||
editorProps: {
|
||||
handleKeyDown: (view, event) => {
|
||||
if (
|
||||
submitOnEnter &&
|
||||
event.key === 'Enter' &&
|
||||
!event.shiftKey &&
|
||||
(!isMobile || event.ctrlKey || event.metaKey) &&
|
||||
// mention list is closed
|
||||
!(view.state as any).mention$.active
|
||||
) {
|
||||
submit()
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
})
|
||||
// insert at mention and focus
|
||||
if (replyToUser) {
|
||||
editor
|
||||
.chain()
|
||||
.setContent({
|
||||
type: 'mention',
|
||||
attrs: { label: replyToUser.username, id: replyToUser.id },
|
||||
})
|
||||
.insertContent(' ')
|
||||
.focus()
|
||||
.run()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editor])
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextEditor editor={editor} upload={upload}>
|
||||
{user && !isSubmitting && (
|
||||
<button
|
||||
className="btn btn-ghost btn-sm px-2 disabled:bg-inherit disabled:text-gray-300"
|
||||
disabled={!editor || editor.isEmpty}
|
||||
onClick={submit}
|
||||
>
|
||||
<PaperAirplaneIcon className="m-0 h-[25px] min-w-[22px] rotate-90 p-0" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isSubmitting && (
|
||||
<LoadingIndicator spinnerClassName={'border-gray-500'} />
|
||||
)}
|
||||
</TextEditor>
|
||||
<Row>
|
||||
{!user && (
|
||||
<button
|
||||
className={'btn btn-outline btn-sm mt-2 normal-case'}
|
||||
onClick={() => submitComment(presetId)}
|
||||
>
|
||||
Add my comment
|
||||
</button>
|
||||
)}
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function getBettorsLargestPositionBeforeTime(
|
||||
contract: Contract,
|
||||
createdTime: number,
|
||||
|
|
|
@ -6,7 +6,6 @@ import { Avatar } from 'web/components/avatar'
|
|||
import { Group } from 'common/group'
|
||||
import { Comment, GroupComment } from 'common/comment'
|
||||
import { createCommentOnGroup } from 'web/lib/firebase/comments'
|
||||
import { CommentInputTextArea } from 'web/components/feed/feed-comments'
|
||||
import { track } from 'web/lib/service/analytics'
|
||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -23,6 +22,7 @@ import { ChevronDownIcon, UsersIcon } from '@heroicons/react/outline'
|
|||
import { setNotificationsAsSeen } from 'web/pages/notifications'
|
||||
import { usePrivateUser } from 'web/hooks/use-user'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import { CommentInputTextArea } from '../comment-input'
|
||||
|
||||
export function GroupChat(props: {
|
||||
messages: GroupComment[]
|
||||
|
|
|
@ -20,7 +20,7 @@ import { listAllCommentsOnPost } from 'web/lib/firebase/comments'
|
|||
import { PostComment } from 'common/comment'
|
||||
import { CommentTipMap, useTipTxns } from 'web/hooks/use-tip-txns'
|
||||
import { groupBy, sortBy } from 'lodash'
|
||||
import { PostCommentThread, CommentInput } from 'web/posts/post-comments'
|
||||
import { PostCommentInput, PostCommentThread } from 'web/posts/post-comments'
|
||||
import { useCommentsOnPost } from 'web/hooks/use-comments'
|
||||
|
||||
export async function getStaticProps(props: { params: { slugs: string[] } }) {
|
||||
|
@ -137,11 +137,7 @@ export function PostCommentsActivity(props: {
|
|||
|
||||
return (
|
||||
<>
|
||||
<CommentInput
|
||||
className="mb-5"
|
||||
post={post}
|
||||
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
||||
/>
|
||||
<PostCommentInput post={post} />
|
||||
{topLevelComments.map((parent) => (
|
||||
<PostCommentThread
|
||||
key={parent.id}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { track } from '@amplitude/analytics-browser'
|
||||
import { Editor } from '@tiptap/core'
|
||||
import clsx from 'clsx'
|
||||
import { PostComment } from 'common/comment'
|
||||
import { Post } from 'common/post'
|
||||
|
@ -7,19 +8,16 @@ import { Dictionary } from 'lodash'
|
|||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Avatar } from 'web/components/avatar'
|
||||
import { Content, useTextEditor } from 'web/components/editor'
|
||||
import { CommentInput } from 'web/components/comment-input'
|
||||
import { Content } from 'web/components/editor'
|
||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
||||
import { CommentInputTextArea } from 'web/components/feed/feed-comments'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Tipper } from 'web/components/tipper'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import {
|
||||
createCommentOnPost,
|
||||
MAX_COMMENT_LENGTH,
|
||||
} from 'web/lib/firebase/comments'
|
||||
import { createCommentOnPost } from 'web/lib/firebase/comments'
|
||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||
|
||||
export function PostCommentThread(props: {
|
||||
|
@ -30,8 +28,7 @@ export function PostCommentThread(props: {
|
|||
parentComment: PostComment
|
||||
commentsByUserId: Dictionary<PostComment[]>
|
||||
}) {
|
||||
const { user, post, threadComments, commentsByUserId, tips, parentComment } =
|
||||
props
|
||||
const { post, threadComments, tips, parentComment } = props
|
||||
const [showReply, setShowReply] = useState(false)
|
||||
const [replyTo, setReplyTo] = useState<{ id: string; username: string }>()
|
||||
|
||||
|
@ -63,9 +60,8 @@ export function PostCommentThread(props: {
|
|||
className="absolute -left-1 -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CommentInput
|
||||
<PostCommentInput
|
||||
post={post}
|
||||
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
||||
parentCommentId={parentComment.id}
|
||||
replyToUser={replyTo}
|
||||
onSubmitComment={() => setShowReply(false)}
|
||||
|
@ -76,6 +72,34 @@ export function PostCommentThread(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export function PostCommentInput(props: {
|
||||
post: Post
|
||||
parentCommentId?: string
|
||||
replyToUser?: { id: string; username: string }
|
||||
onSubmitComment?: () => void
|
||||
}) {
|
||||
const user = useUser()
|
||||
|
||||
const { post, parentCommentId, replyToUser } = props
|
||||
|
||||
async function onSubmitComment(editor: Editor) {
|
||||
if (!user) {
|
||||
track('sign in to comment')
|
||||
return await firebaseLogin()
|
||||
}
|
||||
await createCommentOnPost(post.id, editor.getJSON(), user, parentCommentId)
|
||||
props.onSubmitComment?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<CommentInput
|
||||
replyToUser={replyToUser}
|
||||
parentCommentId={parentCommentId}
|
||||
onSubmitComment={onSubmitComment}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function PostComment(props: {
|
||||
post: Post
|
||||
comment: PostComment
|
||||
|
@ -84,7 +108,7 @@ export function PostComment(props: {
|
|||
probAtCreatedTime?: number
|
||||
onReplyClick?: (comment: PostComment) => void
|
||||
}) {
|
||||
const { post, comment, tips, indent, probAtCreatedTime, onReplyClick } = props
|
||||
const { post, comment, tips, indent, onReplyClick } = props
|
||||
const { text, content, userUsername, userName, userAvatarUrl, createdTime } =
|
||||
comment
|
||||
|
||||
|
@ -147,70 +171,3 @@ export function PostComment(props: {
|
|||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export function CommentInput(props: {
|
||||
post: Post
|
||||
commentsByCurrentUser: PostComment[]
|
||||
className?: string
|
||||
replyToUser?: { id: string; username: string }
|
||||
// Reply to a free response answer
|
||||
parentAnswerOutcome?: string
|
||||
// Reply to another comment
|
||||
parentCommentId?: string
|
||||
onSubmitComment?: () => void
|
||||
}) {
|
||||
const {
|
||||
post,
|
||||
className,
|
||||
parentAnswerOutcome,
|
||||
parentCommentId,
|
||||
replyToUser,
|
||||
onSubmitComment,
|
||||
} = props
|
||||
const user = useUser()
|
||||
const { editor, upload } = useTextEditor({
|
||||
simple: true,
|
||||
max: MAX_COMMENT_LENGTH,
|
||||
placeholder:
|
||||
!!parentCommentId || !!parentAnswerOutcome
|
||||
? 'Write a reply...'
|
||||
: 'Write a comment...',
|
||||
})
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
async function submitComment(betId: string | undefined) {
|
||||
if (!user) {
|
||||
track('sign in to comment')
|
||||
return await firebaseLogin()
|
||||
}
|
||||
if (!editor || editor.isEmpty || isSubmitting) return
|
||||
setIsSubmitting(true)
|
||||
await createCommentOnPost(post.id, editor.getJSON(), user, parentCommentId)
|
||||
onSubmitComment?.()
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
if (user?.isBannedFromPosting) return <></>
|
||||
|
||||
return (
|
||||
<Row className={clsx(className, 'mb-2 gap-1 sm:gap-2')}>
|
||||
<Avatar
|
||||
avatarUrl={user?.avatarUrl}
|
||||
username={user?.username}
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
/>
|
||||
<div className="min-w-0 flex-1 pl-0.5 text-sm">
|
||||
<div className="mb-1 text-gray-500"></div>
|
||||
<CommentInputTextArea
|
||||
editor={editor}
|
||||
upload={upload}
|
||||
replyToUser={replyToUser}
|
||||
user={user}
|
||||
submitComment={submitComment}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user