From d8b2f14762abc39e28c44b6749f9edd50802f68f Mon Sep 17 00:00:00 2001 From: Sinclair Chen Date: Fri, 29 Jul 2022 14:29:01 -0700 Subject: [PATCH] Switch comments/chat to rich text editor --- common/comment.ts | 6 +- web/components/comments-list.tsx | 6 +- web/components/editor.tsx | 9 +- .../feed/feed-answer-comment-group.tsx | 5 +- web/components/feed/feed-comments.tsx | 115 ++++++------------ web/components/groups/group-chat.tsx | 81 ++++++------ web/lib/firebase/comments.ts | 9 +- 7 files changed, 100 insertions(+), 131 deletions(-) diff --git a/common/comment.ts b/common/comment.ts index 0d0c4daf..a217b292 100644 --- a/common/comment.ts +++ b/common/comment.ts @@ -1,3 +1,5 @@ +import type { JSONContent } from '@tiptap/core' + // Currently, comments are created after the bet, not atomically with the bet. // They're uniquely identified by the pair contractId/betId. export type Comment = { @@ -9,7 +11,9 @@ export type Comment = { replyToCommentId?: string userId: string - text: string + /** @deprecated - content now stored as JSON in content*/ + text?: string + content: JSONContent createdTime: number // Denormalized, for rendering comments diff --git a/web/components/comments-list.tsx b/web/components/comments-list.tsx index f8e1d7e1..912023ad 100644 --- a/web/components/comments-list.tsx +++ b/web/components/comments-list.tsx @@ -10,6 +10,7 @@ import { User } from 'common/user' import { Col } from './layout/col' import { Linkify } from './linkify' import { groupBy } from 'lodash' +import { Content } from './editor' export function UserCommentsList(props: { user: User @@ -50,7 +51,8 @@ export function UserCommentsList(props: { function ProfileComment(props: { comment: Comment; className?: string }) { const { comment, className } = props - const { text, userUsername, userName, userAvatarUrl, createdTime } = comment + const { text, content, userUsername, userName, userAvatarUrl, createdTime } = + comment // TODO: find and attach relevant bets by comment betId at some point return ( @@ -64,7 +66,7 @@ function ProfileComment(props: { comment: Comment; className?: string }) { />{' '}

- +
) diff --git a/web/components/editor.tsx b/web/components/editor.tsx index 963cea7e..868c2129 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -41,14 +41,16 @@ export function useTextEditor(props: { max?: number defaultValue?: Content disabled?: boolean + simple?: boolean }) { - const { placeholder, max, defaultValue = '', disabled } = props + const { placeholder, max, defaultValue = '', disabled, simple } = props const users = useUsers() const editorClass = clsx( proseClass, - 'min-h-[6em] resize-none outline-none border-none pt-3 px-4 focus:ring-0' + !simple && 'min-h-[6em]', + 'resize-none outline-none border-none pt-3 px-4 focus:ring-0' ) const editor = useEditor( @@ -56,7 +58,8 @@ export function useTextEditor(props: { editorProps: { attributes: { class: editorClass } }, extensions: [ StarterKit.configure({ - heading: { levels: [1, 2, 3] }, + heading: simple ? false : { levels: [1, 2, 3] }, + horizontalRule: simple ? false : {}, }), Placeholder.configure({ placeholder, diff --git a/web/components/feed/feed-answer-comment-group.tsx b/web/components/feed/feed-answer-comment-group.tsx index aabb1081..ee7b1695 100644 --- a/web/components/feed/feed-answer-comment-group.tsx +++ b/web/components/feed/feed-answer-comment-group.tsx @@ -72,7 +72,7 @@ export function FeedAnswerCommentGroup(props: { (comment?: Comment, answer?: Answer) => { setReplyToUsername(comment?.userUsername ?? answer?.username ?? '') setShowReply(true) - inputRef?.focus() + // TODO: focus } ) @@ -80,7 +80,7 @@ export function FeedAnswerCommentGroup(props: { // Only show one comment input for a bet at a time if ( betsByCurrentUser.length > 1 && - inputRef?.textContent?.length === 0 && + // inputRef?.textContent?.length === 0 && //TODO: editor.isEmpty betsByCurrentUser.sort((a, b) => b.createdTime - a.createdTime)[0] ?.outcome !== answer.number.toString() ) @@ -173,7 +173,6 @@ export function FeedAnswerCommentGroup(props: { commentsByCurrentUser={commentsByCurrentUser} parentAnswerOutcome={answer.number.toString()} replyToUsername={replyToUsername} - setRef={setInputRef} onSubmitComment={() => { setShowReply(false) setReplyToUsername('') diff --git a/web/components/feed/feed-comments.tsx b/web/components/feed/feed-comments.tsx index f4c6eb74..cb6cc798 100644 --- a/web/components/feed/feed-comments.tsx +++ b/web/components/feed/feed-comments.tsx @@ -19,8 +19,6 @@ import { createCommentOnContract, MAX_COMMENT_LENGTH, } from 'web/lib/firebase/comments' -import Textarea from 'react-expanding-textarea' -import { Linkify } from 'web/components/linkify' import { SiteLink } from 'web/components/site-link' import { BetStatusText } from 'web/components/feed/feed-bets' import { Col } from 'web/components/layout/col' @@ -28,10 +26,11 @@ 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 { useEvent } from 'web/hooks/use-event' 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 { Editor, JSONContent } from '@tiptap/react' export function FeedCommentThread(props: { contract: Contract @@ -60,15 +59,13 @@ export function FeedCommentThread(props: { parentComment.id && comment.replyToCommentId === parentComment.id ) commentsList.unshift(parentComment) - const [inputRef, setInputRef] = useState(null) + function scrollAndOpenReplyInput(comment: Comment) { setReplyToUsername(comment.userUsername) setShowReply(true) - inputRef?.focus() + //TODO focus } - useEffect(() => { - if (showReply && inputRef) inputRef.focus() - }, [inputRef, showReply]) + return ( { setShowReply(false) setReplyToUsername('') @@ -195,7 +191,8 @@ export function FeedComment(props: { truncate, onReplyClick, } = props - const { text, userUsername, userName, userAvatarUrl, createdTime } = comment + const { text, content, userUsername, userName, userAvatarUrl, createdTime } = + comment let betOutcome: string | undefined, bought: string | undefined, money: string | undefined @@ -277,7 +274,7 @@ export function FeedComment(props: { /> @@ -346,7 +343,6 @@ export function CommentInput(props: { betsByCurrentUser: Bet[] commentsByCurrentUser: Comment[] replyToUsername?: string - setRef?: (ref: HTMLTextAreaElement) => void // Reply to a free response answer parentAnswerOutcome?: string // Reply to another comment @@ -361,10 +357,16 @@ export function CommentInput(props: { parentCommentId, replyToUsername, onSubmitComment, - setRef, } = props const user = useUser() - const [comment, setComment] = useState('') + 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( @@ -380,18 +382,17 @@ export function CommentInput(props: { track('sign in to comment') return await firebaseLogin() } - if (!comment || isSubmitting) return + if (!editor || editor.isEmpty || isSubmitting) return setIsSubmitting(true) await createCommentOnContract( contract.id, - comment, + editor.getJSON(), user, betId, parentAnswerOutcome, parentCommentId ) onSubmitComment?.() - setComment('') setIsSubmitting(false) } @@ -446,14 +447,12 @@ export function CommentInput(props: { )} @@ -465,81 +464,43 @@ export function CommentInput(props: { export function CommentInputTextArea(props: { user: User | undefined | null - isReply: boolean replyToUsername: string - commentText: string - setComment: (text: string) => void + editor: Editor | null + upload: any submitComment: (id?: string) => void isSubmitting: boolean - setRef?: (ref: HTMLTextAreaElement) => void presetId?: string - enterToSubmitOnDesktop?: boolean }) { const { - isReply, - setRef, user, - commentText, - setComment, + editor, + upload, submitComment, presetId, isSubmitting, replyToUsername, - enterToSubmitOnDesktop, } = props const { width } = useWindowSize() - const memoizedSetComment = useEvent(setComment) useEffect(() => { - if (!replyToUsername || !user || replyToUsername === user.username) return - const replacement = `@${replyToUsername} ` - memoizedSetComment(replacement + commentText.replace(replacement, '')) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [user, replyToUsername, memoizedSetComment]) + editor?.setEditable(!isSubmitting) + }, [isSubmitting, editor]) + + // TODO: make at mention show up at beginning return ( <> -