2023-04-30 16:45:59 +00:00
|
|
|
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
|
2022-09-09 11:53:35 +00:00
|
|
|
import { clsx } from 'clsx'
|
2023-08-05 06:41:52 +00:00
|
|
|
import { getPagePath } from '@nanostores/router'
|
|
|
|
|
2022-10-07 11:02:34 +00:00
|
|
|
import MD from './MD'
|
2023-08-05 06:41:52 +00:00
|
|
|
import { AuthorCard } from '../Author/AuthorCard'
|
2023-08-11 16:42:41 +00:00
|
|
|
import { Userpic } from '../Author/Userpic'
|
2023-08-05 06:41:52 +00:00
|
|
|
import { CommentRatingControl } from './CommentRatingControl'
|
|
|
|
import { CommentDate } from './CommentDate'
|
|
|
|
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
|
|
|
|
import { Icon } from '../_shared/Icon'
|
|
|
|
|
2023-01-20 04:40:55 +00:00
|
|
|
import { useSession } from '../../context/session'
|
2023-08-05 06:41:52 +00:00
|
|
|
import { useLocalize } from '../../context/localize'
|
2023-02-17 09:21:02 +00:00
|
|
|
import { useReactions } from '../../context/reactions'
|
|
|
|
import { useSnackbar } from '../../context/snackbar'
|
2023-08-05 06:41:52 +00:00
|
|
|
import { useConfirm } from '../../context/confirm'
|
|
|
|
|
2023-08-07 09:02:58 +00:00
|
|
|
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
|
2023-05-17 21:10:15 +00:00
|
|
|
import { router } from '../../stores/router'
|
2023-08-05 06:41:52 +00:00
|
|
|
|
|
|
|
import styles from './Comment.module.scss'
|
2023-03-06 14:06:48 +00:00
|
|
|
|
2023-07-24 08:58:07 +00:00
|
|
|
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
|
2022-09-09 11:53:35 +00:00
|
|
|
|
2023-01-20 04:40:55 +00:00
|
|
|
type Props = {
|
|
|
|
comment: Reaction
|
2022-09-09 11:53:35 +00:00
|
|
|
compact?: boolean
|
2023-01-26 06:59:43 +00:00
|
|
|
isArticleAuthor?: boolean
|
2023-02-17 09:21:02 +00:00
|
|
|
sortedComments?: Reaction[]
|
2023-03-06 14:06:48 +00:00
|
|
|
lastSeen?: Date
|
2023-05-17 21:10:15 +00:00
|
|
|
class?: string
|
|
|
|
showArticleLink?: boolean
|
2023-10-16 07:40:34 +00:00
|
|
|
clickedReply?: (id: number) => void
|
|
|
|
clickedReplyId?: number
|
2023-01-20 04:40:55 +00:00
|
|
|
}
|
|
|
|
|
2023-01-04 13:26:18 +00:00
|
|
|
export const Comment = (props: Props) => {
|
2023-02-17 09:21:02 +00:00
|
|
|
const { t } = useLocalize()
|
2022-11-26 21:27:54 +00:00
|
|
|
const [isReplyVisible, setIsReplyVisible] = createSignal(false)
|
2023-10-12 14:18:01 +00:00
|
|
|
const [loading, setLoading] = createSignal(false)
|
|
|
|
const [editMode, setEditMode] = createSignal(false)
|
|
|
|
const [clearEditor, setClearEditor] = createSignal(false)
|
2023-01-20 04:40:55 +00:00
|
|
|
const { session } = useSession()
|
|
|
|
|
2023-02-17 09:21:02 +00:00
|
|
|
const {
|
|
|
|
actions: { createReaction, deleteReaction, updateReaction }
|
|
|
|
} = useReactions()
|
|
|
|
|
2023-08-05 06:41:52 +00:00
|
|
|
const {
|
|
|
|
actions: { showConfirm }
|
|
|
|
} = useConfirm()
|
2023-08-14 14:48:27 +00:00
|
|
|
|
2023-02-17 09:21:02 +00:00
|
|
|
const {
|
|
|
|
actions: { showSnackbar }
|
|
|
|
} = useSnackbar()
|
|
|
|
|
2023-03-06 14:06:48 +00:00
|
|
|
const isCommentAuthor = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug)
|
2022-11-26 21:27:54 +00:00
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
const comment = createMemo(() => props.comment)
|
2022-11-26 01:46:34 +00:00
|
|
|
const body = createMemo(() => (comment().body || '').trim())
|
2023-01-20 04:40:55 +00:00
|
|
|
const remove = async () => {
|
2022-09-09 11:53:35 +00:00
|
|
|
if (comment()?.id) {
|
2023-01-20 04:40:55 +00:00
|
|
|
try {
|
2023-10-16 15:57:29 +00:00
|
|
|
const isConfirmed = await showConfirm({
|
|
|
|
confirmBody: t('Are you sure you want to delete this comment?'),
|
|
|
|
confirmButtonLabel: t('Delete'),
|
|
|
|
confirmButtonVariant: 'danger',
|
|
|
|
declineButtonVariant: 'primary'
|
|
|
|
})
|
2023-08-05 06:41:52 +00:00
|
|
|
|
|
|
|
if (isConfirmed) {
|
|
|
|
await deleteReaction(comment().id)
|
|
|
|
|
|
|
|
await showSnackbar({ body: t('Comment successfully deleted') })
|
|
|
|
}
|
2023-01-20 04:40:55 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.error('[deleteReaction]', error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleCreate = async (value) => {
|
|
|
|
try {
|
|
|
|
setLoading(true)
|
2023-02-17 09:21:02 +00:00
|
|
|
await createReaction({
|
|
|
|
kind: ReactionKind.Comment,
|
|
|
|
replyTo: props.comment.id,
|
|
|
|
body: value,
|
|
|
|
shout: props.comment.shout.id
|
|
|
|
})
|
2023-10-12 14:18:01 +00:00
|
|
|
setClearEditor(true)
|
2023-01-20 04:40:55 +00:00
|
|
|
setIsReplyVisible(false)
|
|
|
|
setLoading(false)
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[handleCreate reaction]:', error)
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
2023-10-12 14:18:01 +00:00
|
|
|
setClearEditor(false)
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
2023-01-26 06:59:43 +00:00
|
|
|
|
2023-02-07 12:48:45 +00:00
|
|
|
const toggleEditMode = () => {
|
|
|
|
setEditMode((oldEditMode) => !oldEditMode)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleUpdate = async (value) => {
|
|
|
|
setLoading(true)
|
|
|
|
try {
|
|
|
|
await updateReaction(props.comment.id, {
|
|
|
|
kind: ReactionKind.Comment,
|
|
|
|
body: value,
|
|
|
|
shout: props.comment.shout.id
|
|
|
|
})
|
|
|
|
setEditMode(false)
|
|
|
|
setLoading(false)
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[handleCreate reaction]:', error)
|
|
|
|
}
|
|
|
|
}
|
2022-09-09 11:53:35 +00:00
|
|
|
|
2023-03-06 14:06:48 +00:00
|
|
|
const createdAt = new Date(comment()?.createdAt)
|
2023-04-17 10:31:20 +00:00
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
return (
|
2023-05-17 21:10:15 +00:00
|
|
|
<li
|
2023-05-26 11:30:27 +00:00
|
|
|
id={`comment_${comment().id}`}
|
2023-05-17 21:42:42 +00:00
|
|
|
class={clsx(styles.comment, props.class, {
|
2023-05-17 21:10:15 +00:00
|
|
|
[styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen
|
|
|
|
})}
|
|
|
|
>
|
2022-09-09 11:53:35 +00:00
|
|
|
<Show when={!!body()}>
|
2022-11-26 21:27:54 +00:00
|
|
|
<div class={styles.commentContent}>
|
2022-09-09 11:53:35 +00:00
|
|
|
<Show
|
|
|
|
when={!props.compact}
|
|
|
|
fallback={
|
2022-11-27 17:02:04 +00:00
|
|
|
<div>
|
2023-02-17 09:21:02 +00:00
|
|
|
<Userpic
|
2023-08-11 16:42:41 +00:00
|
|
|
name={comment().createdBy.name}
|
|
|
|
userpic={comment().createdBy.userpic}
|
2023-02-17 09:21:02 +00:00
|
|
|
isBig={false}
|
|
|
|
class={clsx({
|
|
|
|
[styles.compactUserpic]: props.compact
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
<small>
|
2023-10-16 17:24:33 +00:00
|
|
|
<a href={`#comment_${comment()?.id}`}>{comment()?.shout.title || ''}</a>
|
2022-11-27 17:02:04 +00:00
|
|
|
</small>
|
2022-09-09 11:53:35 +00:00
|
|
|
</div>
|
|
|
|
}
|
|
|
|
>
|
2022-11-26 21:27:54 +00:00
|
|
|
<div class={styles.commentDetails}>
|
|
|
|
<div class={styles.commentAuthor}>
|
2022-09-09 11:53:35 +00:00
|
|
|
<AuthorCard
|
|
|
|
author={comment()?.createdBy as Author}
|
|
|
|
hideDescription={true}
|
|
|
|
hideFollow={true}
|
2022-11-26 21:27:54 +00:00
|
|
|
isComments={true}
|
|
|
|
hasLink={true}
|
2022-09-09 11:53:35 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
2023-01-26 06:59:43 +00:00
|
|
|
<Show when={props.isArticleAuthor}>
|
|
|
|
<div class={styles.articleAuthor}>{t('Author')}</div>
|
|
|
|
</Show>
|
|
|
|
|
2023-05-17 21:10:15 +00:00
|
|
|
<Show when={props.showArticleLink}>
|
|
|
|
<div class={styles.articleLink}>
|
|
|
|
<Icon name="arrow-right" class={styles.articleLinkIcon} />
|
2023-05-26 11:30:27 +00:00
|
|
|
<a
|
|
|
|
href={`${getPagePath(router, 'article', { slug: comment().shout.slug })}?commentId=${
|
|
|
|
comment().id
|
|
|
|
}`}
|
|
|
|
>
|
2023-05-17 21:10:15 +00:00
|
|
|
{comment().shout.title}
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
|
2023-06-12 17:58:02 +00:00
|
|
|
<CommentDate comment={comment()} isShort={true} />
|
2023-05-17 21:10:15 +00:00
|
|
|
|
2023-03-09 12:34:08 +00:00
|
|
|
<CommentRatingControl comment={comment()} />
|
2022-09-09 11:53:35 +00:00
|
|
|
</div>
|
|
|
|
</Show>
|
2023-10-16 17:24:33 +00:00
|
|
|
<div class={styles.commentBody}>
|
2023-02-07 12:48:45 +00:00
|
|
|
<Show when={editMode()} fallback={<MD body={body()} />}>
|
2023-02-11 09:32:52 +00:00
|
|
|
<Suspense fallback={<p>{t('Loading')}</p>}>
|
2023-07-24 08:58:07 +00:00
|
|
|
<SimplifiedEditor
|
2023-07-27 17:38:38 +00:00
|
|
|
initialContent={comment().body}
|
|
|
|
submitButtonText={t('Save')}
|
2023-07-24 08:58:07 +00:00
|
|
|
quoteEnabled={true}
|
|
|
|
imageEnabled={true}
|
|
|
|
placeholder={t('Write a comment...')}
|
|
|
|
onSubmit={(value) => handleUpdate(value)}
|
2023-10-16 07:40:34 +00:00
|
|
|
submitByCtrlEnter={true}
|
2023-10-12 14:18:01 +00:00
|
|
|
setClear={clearEditor()}
|
2023-07-24 08:58:07 +00:00
|
|
|
/>
|
2023-02-07 12:48:45 +00:00
|
|
|
</Suspense>
|
|
|
|
</Show>
|
2022-09-09 11:53:35 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<Show when={!props.compact}>
|
2022-11-26 21:27:54 +00:00
|
|
|
<div class={styles.commentControls}>
|
2023-02-17 09:21:02 +00:00
|
|
|
<ShowIfAuthenticated>
|
|
|
|
<button
|
|
|
|
disabled={loading()}
|
2023-10-16 07:40:34 +00:00
|
|
|
onClick={() => {
|
|
|
|
setIsReplyVisible(!isReplyVisible())
|
|
|
|
props.clickedReply(props.comment.id)
|
|
|
|
}}
|
2023-02-17 09:21:02 +00:00
|
|
|
class={clsx(styles.commentControl, styles.commentControlReply)}
|
|
|
|
>
|
|
|
|
<Icon name="reply" class={styles.icon} />
|
|
|
|
{loading() ? t('Loading') : t('Reply')}
|
|
|
|
</button>
|
|
|
|
</ShowIfAuthenticated>
|
2023-03-06 14:06:48 +00:00
|
|
|
<Show when={isCommentAuthor()}>
|
2023-02-07 12:48:45 +00:00
|
|
|
<button
|
|
|
|
class={clsx(styles.commentControl, styles.commentControlEdit)}
|
|
|
|
onClick={toggleEditMode}
|
|
|
|
>
|
|
|
|
<Icon name="edit" class={styles.icon} />
|
|
|
|
{t('Edit')}
|
|
|
|
</button>
|
2022-11-26 21:27:54 +00:00
|
|
|
<button
|
|
|
|
class={clsx(styles.commentControl, styles.commentControlDelete)}
|
|
|
|
onClick={() => remove()}
|
|
|
|
>
|
|
|
|
<Icon name="delete" class={styles.icon} />
|
2022-09-09 11:53:35 +00:00
|
|
|
{t('Delete')}
|
|
|
|
</button>
|
|
|
|
</Show>
|
|
|
|
|
2023-01-30 10:39:36 +00:00
|
|
|
{/*<SharePopup*/}
|
|
|
|
{/* title={'artile.title'}*/}
|
|
|
|
{/* description={getDescription(body())}*/}
|
|
|
|
{/* containerCssClass={stylesHeader.control}*/}
|
|
|
|
{/* trigger={*/}
|
|
|
|
{/* <button class={clsx(styles.commentControl, styles.commentControlShare)}>*/}
|
|
|
|
{/* <Icon name="share" class={styles.icon} />*/}
|
|
|
|
{/* {t('Share')}*/}
|
|
|
|
{/* </button>*/}
|
|
|
|
{/* }*/}
|
|
|
|
{/*/>*/}
|
2022-11-26 21:27:54 +00:00
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
{/*<button*/}
|
2022-11-26 21:27:54 +00:00
|
|
|
{/* class={clsx(styles.commentControl, styles.commentControlComplain)}*/}
|
2022-09-09 11:53:35 +00:00
|
|
|
{/* onClick={() => showModal('reportComment')}*/}
|
|
|
|
{/*>*/}
|
|
|
|
{/* {t('Report')}*/}
|
|
|
|
{/*</button>*/}
|
|
|
|
</div>
|
2022-11-26 21:27:54 +00:00
|
|
|
|
2023-10-16 07:40:34 +00:00
|
|
|
<Show when={isReplyVisible() && props.clickedReplyId === props.comment.id}>
|
2023-02-07 12:48:45 +00:00
|
|
|
<Suspense fallback={<p>{t('Loading')}</p>}>
|
2023-07-24 08:58:07 +00:00
|
|
|
<SimplifiedEditor
|
|
|
|
quoteEnabled={true}
|
|
|
|
imageEnabled={true}
|
|
|
|
placeholder={t('Write a comment...')}
|
2023-04-17 10:31:20 +00:00
|
|
|
onSubmit={(value) => handleCreate(value)}
|
2023-10-16 07:40:34 +00:00
|
|
|
submitByCtrlEnter={true}
|
2023-04-17 10:31:20 +00:00
|
|
|
/>
|
2023-02-07 12:48:45 +00:00
|
|
|
</Suspense>
|
2022-11-26 21:27:54 +00:00
|
|
|
</Show>
|
2022-09-09 11:53:35 +00:00
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
</Show>
|
2023-02-17 09:21:02 +00:00
|
|
|
<Show when={props.sortedComments}>
|
2023-01-20 04:40:55 +00:00
|
|
|
<ul>
|
2023-02-17 09:21:02 +00:00
|
|
|
<For each={props.sortedComments.filter((r) => r.replyTo === props.comment.id)}>
|
|
|
|
{(c) => (
|
2023-01-26 06:59:43 +00:00
|
|
|
<Comment
|
2023-02-17 09:21:02 +00:00
|
|
|
sortedComments={props.sortedComments}
|
2023-01-26 06:59:43 +00:00
|
|
|
isArticleAuthor={props.isArticleAuthor}
|
2023-02-17 09:21:02 +00:00
|
|
|
comment={c}
|
2023-03-06 14:06:48 +00:00
|
|
|
lastSeen={props.lastSeen}
|
2023-10-16 07:40:34 +00:00
|
|
|
clickedReply={props.clickedReply}
|
|
|
|
clickedReplyId={props.clickedReplyId}
|
2023-01-26 06:59:43 +00:00
|
|
|
/>
|
|
|
|
)}
|
2023-01-20 04:40:55 +00:00
|
|
|
</For>
|
|
|
|
</ul>
|
|
|
|
</Show>
|
|
|
|
</li>
|
2022-09-09 11:53:35 +00:00
|
|
|
)
|
|
|
|
}
|