webapp/src/components/Article/CommentsTree.tsx

178 lines
5.4 KiB
TypeScript
Raw Normal View History

2023-02-07 12:48:45 +00:00
import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
2023-02-17 09:21:02 +00:00
import { Comment } from './Comment'
2022-11-26 16:51:08 +00:00
import styles from '../../styles/Article.module.scss'
2022-11-26 21:27:54 +00:00
import { clsx } from 'clsx'
2023-02-17 09:21:02 +00:00
import { Loading } from '../_shared/Loading'
import { Author, ReactionKind } from '../../graphql/types.gen'
import { useSession } from '../../context/session'
import CommentEditor from '../_shared/CommentEditor'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
2023-02-17 09:21:02 +00:00
import { Button } from '../_shared/Button'
2023-02-11 10:49:31 +00:00
import { createStorage } from '@solid-primitives/storage'
2023-02-17 09:21:02 +00:00
import { useReactions } from '../../context/reactions'
import { byCreated } from '../../utils/sortby'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
import { useLocalize } from '../../context/localize'
2022-11-26 16:51:08 +00:00
2023-02-17 09:21:02 +00:00
type CommentsOrder = 'createdAt' | 'rating'
2022-11-26 16:51:08 +00:00
type Props = {
commentAuthors: Author[]
shoutSlug: string
shoutId: number
}
export const CommentsTree = (props: Props) => {
2022-11-26 16:51:08 +00:00
const [isCommentsLoading, setIsCommentsLoading] = createSignal(false)
2023-02-17 09:21:02 +00:00
const [commentsOrder, setCommentsOrder] = createSignal<CommentsOrder>('createdAt')
const {
reactionEntities,
actions: { loadReactionsBy, createReaction }
} = useReactions()
2023-02-11 10:49:31 +00:00
2023-02-17 09:21:02 +00:00
const { t } = useLocalize()
// TODO: server side?
const [store, setStore] = createStorage({ api: typeof localStorage === 'undefined' ? {} : localStorage })
const [newReactionsCount, setNewReactionsCount] = createSignal<number>(0)
const comments = createMemo(() =>
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
)
const sortedComments = createMemo(() => {
let newSortedComments = [...comments()]
newSortedComments = newSortedComments.sort(byCreated)
if (commentsOrder() === 'rating') {
newSortedComments = newSortedComments.sort((a, b) => {
if (a.replyTo && b.replyTo) {
return 0
}
const x = (a?.stat && a.stat.rating) || 0
const y = (b?.stat && b.stat.rating) || 0
if (x > y) {
return 1
}
if (x < y) {
return -1
}
return 0
})
}
newSortedComments.reverse()
return newSortedComments
})
const updateNewReactionsCount = () => {
2023-02-11 10:49:31 +00:00
const storeValue = Number(store[`${props.shoutSlug}`])
2023-02-17 09:21:02 +00:00
const setVal = () => setStore(`${props.shoutSlug}`, `${comments().length}`)
2023-02-11 10:49:31 +00:00
if (!store[`${props.shoutSlug}`]) {
setVal()
2023-02-17 09:21:02 +00:00
} else if (storeValue < comments().length) {
setNewReactionsCount(comments().length - storeValue)
2023-02-11 10:49:31 +00:00
setVal()
}
}
2023-02-07 12:48:45 +00:00
const { session } = useSession()
2023-02-17 09:21:02 +00:00
onMount(async () => {
2022-11-26 16:51:08 +00:00
try {
setIsCommentsLoading(true)
2023-02-17 09:21:02 +00:00
await loadReactionsBy({
by: { shout: props.shoutSlug }
2022-11-26 16:51:08 +00:00
})
2023-02-17 09:21:02 +00:00
updateNewReactionsCount()
2022-11-26 16:51:08 +00:00
} finally {
setIsCommentsLoading(false)
}
2023-02-17 09:21:02 +00:00
})
const [submitted, setSubmitted] = createSignal<boolean>(false)
const handleSubmitComment = async (value) => {
try {
2023-02-17 09:21:02 +00:00
await createReaction({
kind: ReactionKind.Comment,
body: value,
shout: props.shoutId
})
setSubmitted(true)
} catch (error) {
console.error('[handleCreate reaction]:', error)
}
}
2022-11-26 16:51:08 +00:00
return (
<div>
<Show when={!isCommentsLoading()} fallback={<Loading />}>
2022-11-26 21:27:54 +00:00
<div class={styles.commentsHeaderWrapper}>
<h2 id="comments" class={styles.commentsHeader}>
2023-02-17 09:21:02 +00:00
{t('Comments')} {comments().length.toString() || ''}
<Show when={newReactionsCount() > 0}>
<span class={styles.newReactions}>&nbsp;+{newReactionsCount()}</span>
2023-02-11 10:49:31 +00:00
</Show>
2022-11-26 21:27:54 +00:00
</h2>
<ul class={clsx(styles.commentsViewSwitcher, 'view-switcher')}>
2023-02-17 09:21:02 +00:00
<li classList={{ selected: commentsOrder() === 'createdAt' }}>
2023-02-07 12:48:45 +00:00
<Button
variant="inline"
value={t('By time')}
onClick={() => {
setCommentsOrder('createdAt')
}}
2023-02-07 12:48:45 +00:00
/>
2022-11-26 21:27:54 +00:00
</li>
<li classList={{ selected: commentsOrder() === 'rating' }}>
2023-02-07 12:48:45 +00:00
<Button
variant="inline"
value={t('By rating')}
onClick={() => {
setCommentsOrder('rating')
}}
2023-02-07 12:48:45 +00:00
/>
2022-11-26 21:27:54 +00:00
</li>
</ul>
</div>
<ul class={styles.comments}>
2023-02-17 09:21:02 +00:00
<For each={sortedComments().filter((r) => !r.replyTo)}>
{(reaction) => (
<Comment
2023-02-17 09:21:02 +00:00
sortedComments={sortedComments()}
isArticleAuthor={Boolean(props.commentAuthors.some((a) => a.slug === session()?.user.slug))}
comment={reaction}
/>
)}
</For>
</ul>
2023-02-17 09:21:02 +00:00
<ShowIfAuthenticated
fallback={
<div class={styles.signInMessage} id="comments">
{t('To write a comment, you must')}&nbsp;
<a href="?modal=auth&mode=register" class={styles.link}>
{t('sign up')}
</a>
&nbsp;{t('or')}&nbsp;
<a href="?modal=auth&mode=login" class={styles.link}>
{t('sign in')}
</a>
</div>
}
>
<CommentEditor
2023-02-07 12:48:45 +00:00
placeholder={t('Write a comment...')}
clear={submitted()}
onSubmit={(value) => handleSubmitComment(value)}
/>
2023-02-17 09:21:02 +00:00
</ShowIfAuthenticated>
2022-11-26 16:51:08 +00:00
</Show>
</div>
2022-11-26 16:51:08 +00:00
)
}