2022-09-07 22:09:20 +00:00
|
|
|
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 { 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
|
2022-09-20 22:25:58 +00:00
|
|
|
onSubmitComment?: (editor: Editor) => void
|
2022-09-07 22:09:20 +00:00
|
|
|
className?: string
|
|
|
|
}) {
|
2022-09-20 22:25:58 +00:00
|
|
|
const { parentAnswerOutcome, parentCommentId, replyToUser, onSubmitComment } =
|
|
|
|
props
|
2022-09-07 22:09:20 +00:00
|
|
|
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)
|
|
|
|
|
2022-09-20 22:25:58 +00:00
|
|
|
async function submitComment() {
|
2022-09-07 22:09:20 +00:00
|
|
|
if (!editor || editor.isEmpty || isSubmitting) return
|
|
|
|
setIsSubmitting(true)
|
2022-09-20 22:25:58 +00:00
|
|
|
onSubmitComment?.(editor)
|
2022-09-07 22:09:20 +00:00
|
|
|
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}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Row>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function CommentInputTextArea(props: {
|
|
|
|
user: User | undefined | null
|
|
|
|
replyToUser?: { id: string; username: string }
|
|
|
|
editor: Editor | null
|
|
|
|
upload: Parameters<typeof TextEditor>[0]['upload']
|
2022-09-20 22:25:58 +00:00
|
|
|
submitComment: () => void
|
2022-09-07 22:09:20 +00:00
|
|
|
isSubmitting: boolean
|
|
|
|
}) {
|
2022-09-20 22:25:58 +00:00
|
|
|
const { user, editor, upload, submitComment, isSubmitting, replyToUser } =
|
|
|
|
props
|
2022-09-07 22:09:20 +00:00
|
|
|
useEffect(() => {
|
|
|
|
editor?.setEditable(!isSubmitting)
|
|
|
|
}, [isSubmitting, editor])
|
|
|
|
|
|
|
|
const submit = () => {
|
2022-09-20 22:25:58 +00:00
|
|
|
submitComment()
|
2022-09-07 22:09:20 +00:00
|
|
|
editor?.commands?.clearContent()
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!editor) {
|
|
|
|
return
|
|
|
|
}
|
2022-09-09 18:09:31 +00:00
|
|
|
// Submit on ctrl+enter or mod+enter key
|
2022-09-07 22:09:20 +00:00
|
|
|
editor.setOptions({
|
|
|
|
editorProps: {
|
|
|
|
handleKeyDown: (view, event) => {
|
|
|
|
if (
|
|
|
|
event.key === 'Enter' &&
|
|
|
|
!event.shiftKey &&
|
2022-09-09 18:09:31 +00:00
|
|
|
(event.ctrlKey || event.metaKey) &&
|
2022-09-07 22:09:20 +00:00
|
|
|
// 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 && (
|
2022-09-20 22:25:58 +00:00
|
|
|
<LoadingIndicator spinnerClassName="border-gray-500" />
|
2022-09-07 22:09:20 +00:00
|
|
|
)}
|
|
|
|
</TextEditor>
|
|
|
|
<Row>
|
|
|
|
{!user && (
|
|
|
|
<button
|
2022-09-20 22:25:58 +00:00
|
|
|
className="btn btn-outline btn-sm mt-2 normal-case"
|
|
|
|
onClick={submitComment}
|
2022-09-07 22:09:20 +00:00
|
|
|
>
|
|
|
|
Add my comment
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</Row>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|