refactoring-ratings
This commit is contained in:
parent
82c6841523
commit
e336754226
|
@ -380,7 +380,7 @@
|
|||
"This email is already taken. If it's you": "This email is already taken. If it's you",
|
||||
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "This functionality is currently not available, we would like to work on this issue. Use the download link.",
|
||||
"This month": "This month",
|
||||
"This post has not been rated yet": "This post has not been rated yet",
|
||||
"No one rated yet": "No one rated yet",
|
||||
"This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted": "This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted",
|
||||
"This way you ll be able to subscribe to authors, interesting topics and customize your feed": "This way you ll be able to subscribe to authors, interesting topics and customize your feed",
|
||||
"This week": "This week",
|
||||
|
|
|
@ -402,7 +402,7 @@
|
|||
"This email is already taken. If it's you": "Такой email уже зарегистрирован. Если это вы",
|
||||
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "В данный момент этот функционал не доступен, бы работаем над этой проблемой. Воспользуйтесь загрузкой по ссылке.",
|
||||
"This month": "За месяц",
|
||||
"This post has not been rated yet": "Эту публикацию еще пока никто не оценил",
|
||||
"No one rated yet": "Ещё никто не оценивал",
|
||||
"This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted": "Так мы поймем, что вы реальный человек, и учтем ваш голос. А вы увидите, как проголосовали другие",
|
||||
"This way you ll be able to subscribe to authors, interesting topics and customize your feed": "Так вы сможете подписаться на авторов, интересные темы и настроить свою ленту",
|
||||
"This week": "За неделю",
|
||||
|
|
|
@ -14,7 +14,7 @@ import { Userpic } from '../../Author/Userpic'
|
|||
import { Icon } from '../../_shared/Icon'
|
||||
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
|
||||
import { CommentDate } from '../CommentDate'
|
||||
import { CommentRatingControl } from '../CommentRatingControl'
|
||||
import { RatingControl as CommentRatingControl } from '../RatingControl'
|
||||
|
||||
import styles from './Comment.module.scss'
|
||||
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createMemo } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useReactions } from '../../context/reactions'
|
||||
import { useSession } from '../../context/session'
|
||||
import { useSnackbar } from '../../context/snackbar'
|
||||
import { Reaction, ReactionKind } from '../../graphql/schema/core.gen'
|
||||
import { loadShout } from '../../stores/zine/articles'
|
||||
import { Popup } from '../_shared/Popup'
|
||||
import { VotersList } from '../_shared/VotersList'
|
||||
|
||||
import styles from './CommentRatingControl.module.scss'
|
||||
|
||||
type Props = {
|
||||
comment: Reaction
|
||||
}
|
||||
|
||||
export const CommentRatingControl = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { author } = useSession()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||
|
||||
const checkReaction = (reactionKind: ReactionKind) =>
|
||||
Object.values(reactionEntities).some(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.slug === author()?.slug &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.reply_to === props.comment.id,
|
||||
)
|
||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
||||
const canVote = createMemo(() => author()?.slug !== props.comment.created_by.slug)
|
||||
|
||||
const commentRatingReactions = createMemo(() =>
|
||||
Object.values(reactionEntities).filter(
|
||||
(r) =>
|
||||
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.reply_to === props.comment.id,
|
||||
),
|
||||
)
|
||||
|
||||
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
|
||||
const reactionToDelete = Object.values(reactionEntities).find(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.slug === author()?.slug &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.reply_to === props.comment.id,
|
||||
)
|
||||
return deleteReaction(reactionToDelete.id)
|
||||
}
|
||||
|
||||
const handleRatingChange = async (isUpvote: boolean) => {
|
||||
try {
|
||||
if (isUpvoted()) {
|
||||
await deleteCommentReaction(ReactionKind.Like)
|
||||
} else if (isDownvoted()) {
|
||||
await deleteCommentReaction(ReactionKind.Dislike)
|
||||
} else {
|
||||
await createReaction({
|
||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||
shout: props.comment.shout.id,
|
||||
reply_to: props.comment.id,
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
showSnackbar({ type: 'error', body: t('Error') })
|
||||
}
|
||||
|
||||
await loadShout(props.comment.shout.slug)
|
||||
await loadReactionsBy({
|
||||
by: { shout: props.comment.shout.slug },
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={styles.commentRating}>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!(canVote() && author())}
|
||||
onClick={() => handleRatingChange(true)}
|
||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
|
||||
[styles.voted]: isUpvoted(),
|
||||
})}
|
||||
/>
|
||||
<Popup
|
||||
trigger={
|
||||
<div
|
||||
class={clsx(styles.commentRatingValue, {
|
||||
[styles.commentRatingPositive]: props.comment.stat.rating > 0,
|
||||
[styles.commentRatingNegative]: props.comment.stat.rating < 0,
|
||||
})}
|
||||
>
|
||||
{props.comment.stat.rating || 0}
|
||||
</div>
|
||||
}
|
||||
variant="tiny"
|
||||
>
|
||||
<VotersList
|
||||
reactions={commentRatingReactions()}
|
||||
fallbackMessage={t('This comment has not yet been rated')}
|
||||
/>
|
||||
</Popup>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!(canVote() && author())}
|
||||
onClick={() => handleRatingChange(false)}
|
||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
||||
[styles.voted]: isDownvoted(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -35,8 +35,8 @@ import { VideoPlayer } from '../_shared/VideoPlayer'
|
|||
import { AudioHeader } from './AudioHeader'
|
||||
import { AudioPlayer } from './AudioPlayer'
|
||||
import { CommentsTree } from './CommentsTree'
|
||||
import { RatingControl as ShoutRatingControl } from './RatingControl'
|
||||
import { SharePopup, getShareUrl } from './SharePopup'
|
||||
import { ShoutRatingControl } from './ShoutRatingControl'
|
||||
|
||||
import stylesHeader from '../Nav/Header/Header.module.scss'
|
||||
import styles from './Article.module.scss'
|
||||
|
@ -313,17 +313,27 @@ export const FullArticle = (props: Props) => {
|
|||
),
|
||||
)
|
||||
const [ratings, setRatings] = createSignal<Reaction[]>([])
|
||||
onMount(async () => {
|
||||
install('G-LQ4B87H8C2')
|
||||
const rrr = await loadReactionsBy({ by: { shout: props.article.slug } })
|
||||
setRatings((_) => rrr.filter((r) => ['LIKE', 'DISLIKE'].includes(r.kind)))
|
||||
setIsReactionsLoaded(true)
|
||||
document.title = props.article.title
|
||||
window?.addEventListener('resize', updateIframeSizes)
|
||||
|
||||
onMount(async () => {
|
||||
document.title = props.article?.title
|
||||
install('G-LQ4B87H8C2')
|
||||
window?.addEventListener('resize', updateIframeSizes)
|
||||
onCleanup(() => window.removeEventListener('resize', updateIframeSizes))
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.article,
|
||||
async (shout: Shout) => {
|
||||
setIsReactionsLoaded(false)
|
||||
const rrr = await loadReactionsBy({ by: { shout: shout?.slug } })
|
||||
setRatings((_) => rrr.filter((r) => ['LIKE', 'DISLIKE'].includes(r.kind)))
|
||||
setIsReactionsLoaded(true)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
const cover = props.article.cover ?? 'production/image/logo_image.png'
|
||||
const ogImage = getOpenGraphImageUrl(cover, {
|
||||
title: props.article.title,
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { Show, Suspense, createEffect, createMemo, createSignal, mergeProps, on } from 'solid-js'
|
||||
import { Show, createEffect, createMemo, createSignal, mergeProps, on } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useReactions } from '../../context/reactions'
|
||||
import { useSession } from '../../context/session'
|
||||
import { Author, Reaction, ReactionKind, Shout } from '../../graphql/schema/core.gen'
|
||||
import { Reaction, ReactionKind, Shout } from '../../graphql/schema/core.gen'
|
||||
import { loadShout } from '../../stores/zine/articles'
|
||||
import { byCreated } from '../../utils/sortby'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Popup } from '../_shared/Popup'
|
||||
import { VotersList } from '../_shared/VotersList'
|
||||
import styles from './ShoutRatingControl.module.scss'
|
||||
|
||||
interface ShoutRatingControlProps {
|
||||
shout: Shout
|
||||
import stylesComment from './CommentRatingControl.module.scss'
|
||||
import stylesShout from './ShoutRatingControl.module.scss'
|
||||
|
||||
interface RatingControlProps {
|
||||
shout?: Shout
|
||||
comment?: Reaction
|
||||
ratings?: Reaction[]
|
||||
class?: string
|
||||
}
|
||||
|
||||
export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||
export const RatingControl = (props: RatingControlProps) => {
|
||||
const { t } = useLocalize()
|
||||
const { author, requireAuthentication } = useSession()
|
||||
const { createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||
|
@ -85,27 +88,66 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
|||
}
|
||||
const isNotDisliked = createMemo(() => !myRate() || myRate()?.kind === ReactionKind.Dislike)
|
||||
const isNotLiked = createMemo(() => !myRate() || myRate()?.kind === ReactionKind.Like)
|
||||
|
||||
const getTrigger = () => {
|
||||
return props.comment ? (
|
||||
<div
|
||||
class={clsx(stylesComment.commentRatingValue, {
|
||||
[stylesComment.commentRatingPositive]: props.comment.stat.rating > 0,
|
||||
[stylesComment.commentRatingNegative]: props.comment.stat.rating < 0,
|
||||
})}
|
||||
>
|
||||
{props.comment.stat.rating || 0}
|
||||
</div>
|
||||
) : (
|
||||
<span class={stylesShout.ratingValue}>{total()}</span>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div class={clsx(styles.rating, props.class)}>
|
||||
<button onClick={() => handleRatingChange(ReactionKind.Dislike)} disabled={isLoading()}>
|
||||
<div class={clsx(props.comment ? stylesComment.commentRating : stylesShout.rating, props.class)}>
|
||||
<button
|
||||
onClick={() => handleRatingChange(ReactionKind.Dislike)}
|
||||
disabled={isLoading()}
|
||||
class={
|
||||
props.comment
|
||||
? clsx(stylesComment.commentRatingControl, stylesComment.commentRatingControlUp, {
|
||||
[stylesComment.voted]: myRate()?.kind === 'LIKE',
|
||||
})
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Show when={!props.comment}>
|
||||
<Icon
|
||||
name={isNotDisliked() ? 'rating-control-less' : 'rating-control-checked'}
|
||||
class={isLoading() ? 'rotating' : ''}
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
|
||||
<Popup trigger={<span class={styles.ratingValue}>{total()}</span>} variant="tiny">
|
||||
<Popup trigger={getTrigger()} variant="tiny">
|
||||
<VotersList
|
||||
reactions={ratings()}
|
||||
fallbackMessage={isLoading() ? t('Loading') : t('This post has not been rated yet')}
|
||||
fallbackMessage={isLoading() ? t('Loading') : t('No one rated yet')}
|
||||
/>
|
||||
</Popup>
|
||||
|
||||
<button onClick={() => handleRatingChange(ReactionKind.Like)} disabled={isLoading()}>
|
||||
<button
|
||||
onClick={() => handleRatingChange(ReactionKind.Like)}
|
||||
disabled={isLoading()}
|
||||
class={
|
||||
props.comment
|
||||
? clsx(stylesComment.commentRatingControl, stylesComment.commentRatingControlDown, {
|
||||
[stylesComment.voted]: myRate()?.kind === 'DISLIKE',
|
||||
})
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Show when={!props.comment}>
|
||||
<Icon
|
||||
name={isNotLiked() ? 'rating-control-more' : 'rating-control-checked'}
|
||||
class={isLoading() ? 'rotating' : ''}
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
)
|
|
@ -10,8 +10,8 @@ import { router, useRouter } from '../../../stores/router'
|
|||
import { capitalize } from '../../../utils/capitalize'
|
||||
import { getDescription } from '../../../utils/meta'
|
||||
import { CoverImage } from '../../Article/CoverImage'
|
||||
import { RatingControl as ShoutRatingControl } from '../../Article/RatingControl'
|
||||
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
|
||||
import { AuthorLink } from '../../Author/AuthorLink'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Image } from '../../_shared/Image'
|
||||
|
|
Loading…
Reference in New Issue
Block a user