2024-06-24 17:50:27 +00:00
|
|
|
import { A } from '@solidjs/router'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { clsx } from 'clsx'
|
2024-02-04 11:25:21 +00:00
|
|
|
import { For, Show, Suspense, createMemo, createSignal, lazy } from 'solid-js'
|
2024-07-04 07:51:15 +00:00
|
|
|
import { Icon } from '~/components/_shared/Icon'
|
|
|
|
import { ShowIfAuthenticated } from '~/components/_shared/ShowIfAuthenticated'
|
2024-07-30 19:06:17 +00:00
|
|
|
import { coreApiUrl } from '~/config'
|
2024-07-04 07:51:15 +00:00
|
|
|
import { useLocalize } from '~/context/localize'
|
|
|
|
import { useReactions } from '~/context/reactions'
|
|
|
|
import { useSession } from '~/context/session'
|
2024-06-24 17:50:27 +00:00
|
|
|
import { useSnackbar, useUI } from '~/context/ui'
|
2024-07-30 19:06:17 +00:00
|
|
|
import { graphqlClientCreate } from '~/graphql/client'
|
2024-06-24 17:50:27 +00:00
|
|
|
import deleteReactionMutation from '~/graphql/mutation/core/reaction-destroy'
|
|
|
|
import {
|
|
|
|
Author,
|
|
|
|
MutationCreate_ReactionArgs,
|
|
|
|
MutationUpdate_ReactionArgs,
|
|
|
|
Reaction,
|
2024-06-26 08:22:05 +00:00
|
|
|
ReactionKind
|
2024-07-04 07:51:15 +00:00
|
|
|
} from '~/graphql/schema/core.gen'
|
2023-12-28 00:30:09 +00:00
|
|
|
import { AuthorLink } from '../../Author/AuthorLink'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Userpic } from '../../Author/Userpic'
|
|
|
|
import { CommentDate } from '../CommentDate'
|
2024-07-26 15:49:15 +00:00
|
|
|
import { CommentRatingControl } from '../CommentRatingControl'
|
2023-08-05 06:41:52 +00:00
|
|
|
import styles from './Comment.module.scss'
|
2023-03-06 14:06:48 +00:00
|
|
|
|
2023-11-13 14:43:08 +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-11-28 13:18:25 +00:00
|
|
|
lastSeen?: number
|
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
|
2024-03-06 11:56:32 +00:00
|
|
|
onDelete?: (id: number) => void
|
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)
|
2024-03-04 10:47:11 +00:00
|
|
|
const [editedBody, setEditedBody] = createSignal<string>()
|
2024-06-24 17:50:27 +00:00
|
|
|
const { session } = useSession()
|
|
|
|
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
2024-09-06 05:13:24 +00:00
|
|
|
const { createShoutReaction, updateShoutReaction } = useReactions()
|
2024-06-24 17:50:27 +00:00
|
|
|
const { showConfirm } = useUI()
|
2024-02-04 17:40:15 +00:00
|
|
|
const { showSnackbar } = useSnackbar()
|
2024-07-30 19:06:17 +00:00
|
|
|
const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token))
|
2024-02-16 17:04:05 +00:00
|
|
|
const canEdit = createMemo(
|
|
|
|
() =>
|
|
|
|
Boolean(author()?.id) &&
|
2024-06-26 08:22:05 +00:00
|
|
|
(props.comment?.created_by?.slug === author()?.slug || session()?.user?.roles?.includes('editor'))
|
2024-02-16 10:21:25 +00:00
|
|
|
)
|
2024-02-16 15:47:48 +00:00
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const body = createMemo(() => (editedBody() ? editedBody()?.trim() : props.comment.body?.trim() || ''))
|
2024-02-04 17:40:15 +00:00
|
|
|
|
2023-01-20 04:40:55 +00:00
|
|
|
const remove = async () => {
|
2024-02-27 11:41:49 +00:00
|
|
|
if (props.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',
|
2024-06-26 08:22:05 +00:00
|
|
|
declineButtonVariant: 'primary'
|
2023-10-16 15:57:29 +00:00
|
|
|
})
|
2023-08-05 06:41:52 +00:00
|
|
|
|
|
|
|
if (isConfirmed) {
|
2024-07-30 19:06:17 +00:00
|
|
|
const resp = await client()
|
|
|
|
?.mutation(deleteReactionMutation, { id: props.comment.id })
|
|
|
|
.toPromise()
|
2024-06-24 17:50:27 +00:00
|
|
|
const result = resp?.data?.delete_reaction
|
|
|
|
const { error } = result
|
2024-03-07 07:20:50 +00:00
|
|
|
const notificationType = error ? 'error' : 'success'
|
|
|
|
const notificationMessage = error
|
|
|
|
? t('Failed to delete comment')
|
|
|
|
: t('Comment successfully deleted')
|
2024-06-24 17:50:27 +00:00
|
|
|
await showSnackbar({
|
|
|
|
type: notificationType,
|
|
|
|
body: notificationMessage,
|
2024-06-26 08:22:05 +00:00
|
|
|
duration: 3
|
2024-06-24 17:50:27 +00:00
|
|
|
})
|
2024-03-07 07:20:50 +00:00
|
|
|
|
|
|
|
if (!error && props.onDelete) {
|
2024-03-06 11:56:32 +00:00
|
|
|
props.onDelete(props.comment.id)
|
|
|
|
}
|
2023-08-05 06:41:52 +00:00
|
|
|
}
|
2023-01-20 04:40:55 +00:00
|
|
|
} catch (error) {
|
2024-03-07 07:20:50 +00:00
|
|
|
await showSnackbar({ body: 'error' })
|
2023-01-20 04:40:55 +00:00
|
|
|
console.error('[deleteReaction]', error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const handleCreate = async (value: string) => {
|
2023-01-20 04:40:55 +00:00
|
|
|
try {
|
|
|
|
setLoading(true)
|
2024-09-06 05:13:24 +00:00
|
|
|
await createShoutReaction({
|
2024-06-24 17:50:27 +00:00
|
|
|
reaction: {
|
|
|
|
kind: ReactionKind.Comment,
|
|
|
|
reply_to: props.comment.id,
|
|
|
|
body: value,
|
2024-06-26 08:22:05 +00:00
|
|
|
shout: props.comment.shout.id
|
|
|
|
}
|
2024-06-24 17:50:27 +00:00
|
|
|
} as MutationCreate_ReactionArgs)
|
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)
|
|
|
|
}
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const handleUpdate = async (value: string) => {
|
2023-02-07 12:48:45 +00:00
|
|
|
setLoading(true)
|
|
|
|
try {
|
2024-09-06 05:13:24 +00:00
|
|
|
const reaction = await updateShoutReaction({
|
2024-06-24 17:50:27 +00:00
|
|
|
reaction: {
|
|
|
|
id: props.comment.id || 0,
|
|
|
|
kind: ReactionKind.Comment,
|
|
|
|
body: value,
|
2024-06-26 08:22:05 +00:00
|
|
|
shout: props.comment.shout.id
|
|
|
|
}
|
2024-06-24 17:50:27 +00:00
|
|
|
} as MutationUpdate_ReactionArgs)
|
2024-03-04 10:47:11 +00:00
|
|
|
if (reaction) {
|
|
|
|
setEditedBody(value)
|
|
|
|
}
|
2023-02-07 12:48:45 +00:00
|
|
|
setEditMode(false)
|
|
|
|
setLoading(false)
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[handleCreate reaction]:', error)
|
|
|
|
}
|
|
|
|
}
|
2022-09-09 11:53:35 +00:00
|
|
|
|
|
|
|
return (
|
2023-05-17 21:10:15 +00:00
|
|
|
<li
|
2024-02-27 11:41:49 +00:00
|
|
|
id={`comment_${props.comment.id}`}
|
|
|
|
class={clsx(styles.comment, props.class, {
|
2024-06-24 17:50:27 +00:00
|
|
|
[styles.isNew]:
|
2024-06-26 08:22:05 +00:00
|
|
|
(props.lastSeen || Date.now()) > (props.comment.updated_at || props.comment.created_at)
|
2024-02-27 11:41:49 +00:00
|
|
|
})}
|
2023-05-17 21:10:15 +00:00
|
|
|
>
|
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
|
2024-06-24 17:50:27 +00:00
|
|
|
name={props.comment.created_by.name || ''}
|
|
|
|
userpic={props.comment.created_by.pic || ''}
|
2023-02-17 09:21:02 +00:00
|
|
|
class={clsx({
|
2024-06-26 08:22:05 +00:00
|
|
|
[styles.compactUserpic]: props.compact
|
2023-02-17 09:21:02 +00:00
|
|
|
})}
|
|
|
|
/>
|
|
|
|
<small>
|
2024-02-27 11:41:49 +00:00
|
|
|
<a href={`#comment_${props.comment?.id}`}>{props.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}>
|
2024-02-27 11:41:49 +00:00
|
|
|
<AuthorLink author={props.comment?.created_by as Author} />
|
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} />
|
2024-06-24 17:50:27 +00:00
|
|
|
<A href={`${props.comment.shout.slug}?commentId=${props.comment.id}`}>
|
2024-02-27 11:41:49 +00:00
|
|
|
{props.comment.shout.title}
|
2024-06-24 17:50:27 +00:00
|
|
|
</A>
|
2023-05-17 21:10:15 +00:00
|
|
|
</div>
|
|
|
|
</Show>
|
2024-02-27 11:41:49 +00:00
|
|
|
<CommentDate showOnHover={true} comment={props.comment} isShort={true} />
|
|
|
|
<CommentRatingControl comment={props.comment} />
|
2022-09-09 11:53:35 +00:00
|
|
|
</div>
|
|
|
|
</Show>
|
2023-10-16 17:24:33 +00:00
|
|
|
<div class={styles.commentBody}>
|
2023-11-13 17:14:58 +00:00
|
|
|
<Show when={editMode()} fallback={<div innerHTML={body()} />}>
|
2023-02-11 09:32:52 +00:00
|
|
|
<Suspense fallback={<p>{t('Loading')}</p>}>
|
2023-07-24 08:58:07 +00:00
|
|
|
<SimplifiedEditor
|
2024-06-24 17:50:27 +00:00
|
|
|
initialContent={editedBody() || props.comment.body || ''}
|
2023-07-27 17:38:38 +00:00
|
|
|
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-11-13 14:43:08 +00:00
|
|
|
onCancel={() => setEditMode(false)}
|
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}>
|
2023-11-13 14:43:08 +00:00
|
|
|
<div>
|
2023-02-17 09:21:02 +00:00
|
|
|
<ShowIfAuthenticated>
|
|
|
|
<button
|
|
|
|
disabled={loading()}
|
2023-10-16 07:40:34 +00:00
|
|
|
onClick={() => {
|
|
|
|
setIsReplyVisible(!isReplyVisible())
|
2024-06-24 17:50:27 +00:00
|
|
|
props.clickedReply?.(props.comment.id)
|
2023-10-16 07:40:34 +00:00
|
|
|
}}
|
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>
|
2024-02-16 10:21:25 +00:00
|
|
|
<Show when={canEdit()}>
|
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*/}
|
2023-12-09 18:35:08 +00:00
|
|
|
{/* title={'article.title'}*/}
|
2023-01-30 10:39:36 +00:00
|
|
|
{/* 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')}*/}
|
|
|
|
{/*>*/}
|
2024-07-03 17:38:43 +00:00
|
|
|
{/* {t('Complain')}*/}
|
2022-09-09 11:53:35 +00:00
|
|
|
{/*</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>
|
2024-06-24 17:50:27 +00:00
|
|
|
<For each={props.sortedComments?.filter((r) => r.reply_to === props.comment.id)}>
|
2023-02-17 09:21:02 +00:00
|
|
|
{(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
|
|
|
)
|
|
|
|
}
|