Compare commits

...

1 Commits

Author SHA1 Message Date
Austin Chen
3b575080b0 Allow editing of comments on a market 2022-08-20 13:04:50 -07:00
3 changed files with 77 additions and 29 deletions

View File

@ -90,8 +90,10 @@ service cloud.firestore {
.hasOnly(['description', 'closeTime', 'question']) .hasOnly(['description', 'closeTime', 'question'])
&& resource.data.creatorId == request.auth.uid; && resource.data.creatorId == request.auth.uid;
allow update: if isAdmin(); allow update: if isAdmin();
// TODO: This runs afoul of FirebaseError: Missing or insufficient permissions
match /comments/{commentId} { match /comments/{commentId} {
allow create: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data); allow create, update: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data);
} }
} }

View File

@ -16,6 +16,7 @@ import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-ti
import { firebaseLogin } from 'web/lib/firebase/users' import { firebaseLogin } from 'web/lib/firebase/users'
import { import {
createCommentOnContract, createCommentOnContract,
editCommentOnContract,
MAX_COMMENT_LENGTH, MAX_COMMENT_LENGTH,
} from 'web/lib/firebase/comments' } from 'web/lib/firebase/comments'
import { BetStatusText } from 'web/components/feed/feed-bets' import { BetStatusText } from 'web/components/feed/feed-bets'
@ -28,7 +29,7 @@ import { Tipper } from '../tipper'
import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns' import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns'
import { useWindowSize } from 'web/hooks/use-window-size' import { useWindowSize } from 'web/hooks/use-window-size'
import { Content, TextEditor, useTextEditor } from '../editor' import { Content, TextEditor, useTextEditor } from '../editor'
import { Editor } from '@tiptap/react' import { Editor, JSONContent } from '@tiptap/react'
export function FeedCommentThread(props: { export function FeedCommentThread(props: {
contract: Contract contract: Contract
@ -113,6 +114,8 @@ export function CommentRepliesList(props: {
scrollAndOpenReplyInput, scrollAndOpenReplyInput,
treatFirstIndexEqually, treatFirstIndexEqually,
} = props } = props
const [editCommentId, setEditCommentId] = useState<string | undefined>()
const user = useUser()
return ( return (
<> <>
{commentsList.map((comment, commentIdx) => ( {commentsList.map((comment, commentIdx) => (
@ -131,12 +134,24 @@ export function CommentRepliesList(props: {
aria-hidden="true" aria-hidden="true"
/> />
)} )}
{editCommentId === comment.id ? (
<CommentInput
contract={contract}
// TODO: These were Copilot-generated, examine more closely
betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
commentsByCurrentUser={commentsList.filter(
(c) => c.userId === user?.id
)}
toEdit={comment}
/>
) : (
<FeedComment <FeedComment
contract={contract} contract={contract}
comment={comment} comment={comment}
tips={tips[comment.id]} tips={tips[comment.id]}
betsBySameUser={betsByUserId[comment.userId] ?? []} betsBySameUser={betsByUserId[comment.userId] ?? []}
onReplyClick={scrollAndOpenReplyInput} onReplyClick={scrollAndOpenReplyInput}
onEditClick={() => setEditCommentId(comment.id)}
probAtCreatedTime={ probAtCreatedTime={
contract.outcomeType === 'BINARY' contract.outcomeType === 'BINARY'
? minBy(bets, (bet) => { ? minBy(bets, (bet) => {
@ -148,6 +163,7 @@ export function CommentRepliesList(props: {
} }
smallAvatar={smallAvatar} smallAvatar={smallAvatar}
/> />
)}
</div> </div>
))} ))}
</> </>
@ -162,6 +178,7 @@ export function FeedComment(props: {
probAtCreatedTime?: number probAtCreatedTime?: number
smallAvatar?: boolean smallAvatar?: boolean
onReplyClick?: (comment: ContractComment) => void onReplyClick?: (comment: ContractComment) => void
onEditClick?: (comment: ContractComment) => void
}) { }) {
const { const {
contract, contract,
@ -170,6 +187,7 @@ export function FeedComment(props: {
betsBySameUser, betsBySameUser,
probAtCreatedTime, probAtCreatedTime,
onReplyClick, onReplyClick,
onEditClick,
} = props } = props
const { text, content, userUsername, userName, userAvatarUrl, createdTime } = const { text, content, userUsername, userName, userAvatarUrl, createdTime } =
comment comment
@ -199,6 +217,9 @@ export function FeedComment(props: {
matchedBet ? [] : betsBySameUser matchedBet ? [] : betsBySameUser
) )
const self = useUser()
const canEdit = self?.id === comment.userId
return ( return (
<Row <Row
className={clsx( className={clsx(
@ -266,6 +287,14 @@ export function FeedComment(props: {
Reply Reply
</button> </button>
)} )}
{canEdit && onEditClick ? (
<button
className="font-bold hover:underline"
onClick={() => onEditClick(comment)}
>
Edit
</button>
) : null}
</Row> </Row>
</div> </div>
</Row> </Row>
@ -326,6 +355,7 @@ export function CommentInput(props: {
// Reply to another comment // Reply to another comment
parentCommentId?: string parentCommentId?: string
onSubmitComment?: () => void onSubmitComment?: () => void
toEdit?: ContractComment
}) { }) {
const { const {
contract, contract,
@ -335,6 +365,7 @@ export function CommentInput(props: {
parentCommentId, parentCommentId,
replyToUser, replyToUser,
onSubmitComment, onSubmitComment,
toEdit,
} = props } = props
const user = useUser() const user = useUser()
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
@ -344,6 +375,7 @@ export function CommentInput(props: {
!!parentCommentId || !!parentAnswerOutcome !!parentCommentId || !!parentAnswerOutcome
? 'Write a reply...' ? 'Write a reply...'
: 'Write a comment...', : 'Write a comment...',
defaultValue: toEdit?.content,
}) })
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
@ -362,6 +394,9 @@ export function CommentInput(props: {
} }
if (!editor || editor.isEmpty || isSubmitting) return if (!editor || editor.isEmpty || isSubmitting) return
setIsSubmitting(true) setIsSubmitting(true)
if (toEdit) {
await editCommentOnContract(toEdit, editor.getJSON())
} else {
await createCommentOnContract( await createCommentOnContract(
contract.id, contract.id,
editor.getJSON(), editor.getJSON(),
@ -370,6 +405,8 @@ export function CommentInput(props: {
parentAnswerOutcome, parentAnswerOutcome,
parentCommentId parentCommentId
) )
}
onSubmitComment?.() onSubmitComment?.()
setIsSubmitting(false) setIsSubmitting(false)
} }

View File

@ -20,6 +20,15 @@ export type { Comment }
export const MAX_COMMENT_LENGTH = 10000 export const MAX_COMMENT_LENGTH = 10000
export async function editCommentOnContract(
toEdit: ContractComment,
newContent: JSONContent
) {
const { id, contractId } = toEdit
const ref = doc(getCommentsCollection(contractId), id)
await setDoc(ref, { content: newContent })
}
export async function createCommentOnContract( export async function createCommentOnContract(
contractId: string, contractId: string,
content: JSONContent, content: JSONContent,