Merge branch 'comments_reactions' into 'dev'
Comments rating See merge request discoursio/discoursio-webapp!38
This commit is contained in:
commit
9e69f280cf
|
@ -201,39 +201,6 @@
|
|||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.commentRating {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.commentRatingValue {
|
||||
padding: 0 0.3em;
|
||||
}
|
||||
|
||||
.commentRatingPositive {
|
||||
color: #2bb452;
|
||||
}
|
||||
|
||||
.commentRatingNegative {
|
||||
color: #d00820;
|
||||
}
|
||||
|
||||
.commentRatingControl {
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.commentRatingControlUp {
|
||||
border-bottom: 8px solid rgb(0 0 0 / 40%);
|
||||
}
|
||||
|
||||
.commentRatingControlDown {
|
||||
border-top: 8px solid rgb(0 0 0 / 40%);
|
||||
}
|
||||
|
||||
.compactUserpic {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
|
|
|
@ -13,7 +13,7 @@ import { useReactions } from '../../context/reactions'
|
|||
import { useSnackbar } from '../../context/snackbar'
|
||||
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import Cookie from 'js-cookie'
|
||||
import { CommentRatingControl } from './CommentRatingControl'
|
||||
|
||||
const CommentEditor = lazy(() => import('../_shared/CommentEditor'))
|
||||
|
||||
|
@ -140,17 +140,7 @@ export const Comment = (props: Props) => {
|
|||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div
|
||||
class={styles.commentRating}
|
||||
classList={{
|
||||
[styles.commentRatingPositive]: comment().stat.rating > 0,
|
||||
[styles.commentRatingNegative]: comment().stat.rating < 0
|
||||
}}
|
||||
>
|
||||
<button class={clsx(styles.commentRatingControl, styles.commentRatingControlUp)} />
|
||||
<div class={styles.commentRatingValue}>{comment().stat.rating || 0}</div>
|
||||
<button class={clsx(styles.commentRatingControl, styles.commentRatingControlDown)} />
|
||||
</div>
|
||||
<CommentRatingControl comment={comment()} />
|
||||
</div>
|
||||
</Show>
|
||||
<div class={styles.commentBody} id={'comment-' + (comment().id || '')}>
|
||||
|
|
45
src/components/Article/CommentRatingControl.module.scss
Normal file
45
src/components/Article/CommentRatingControl.module.scss
Normal file
|
@ -0,0 +1,45 @@
|
|||
.commentRating {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
|
||||
.commentRatingValue {
|
||||
padding: 0 0.3em;
|
||||
margin: 0 0.6rem;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.commentRatingPositive {
|
||||
color: #2bb452;
|
||||
}
|
||||
|
||||
.commentRatingNegative {
|
||||
color: #d00820;
|
||||
}
|
||||
|
||||
.commentRatingControl {
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.commentRatingControlUp {
|
||||
border-bottom: 8px solid rgb(0 0 0 / 40%);
|
||||
&.voted {
|
||||
border-bottom-color: #2bb452;
|
||||
}
|
||||
}
|
||||
|
||||
.commentRatingControlDown {
|
||||
border-top: 8px solid rgb(0 0 0 / 40%);
|
||||
&.voted {
|
||||
border-top-color: #d00820;
|
||||
}
|
||||
}
|
||||
}
|
125
src/components/Article/CommentRatingControl.tsx
Normal file
125
src/components/Article/CommentRatingControl.tsx
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { clsx } from 'clsx'
|
||||
import styles from './CommentRatingControl.module.scss'
|
||||
import type { Reaction } from '../../graphql/types.gen'
|
||||
import { ReactionKind } from '../../graphql/types.gen'
|
||||
import { useSession } from '../../context/session'
|
||||
import { useReactions } from '../../context/reactions'
|
||||
import { createMemo, For } from 'solid-js'
|
||||
import { loadShout } from '../../stores/zine/articles'
|
||||
import { Popup } from '../_shared/Popup'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useSnackbar } from '../../context/snackbar'
|
||||
|
||||
type Props = {
|
||||
comment: Reaction
|
||||
}
|
||||
|
||||
export const CommentRatingControl = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { userSlug } = useSession()
|
||||
const {
|
||||
actions: { showSnackbar }
|
||||
} = useSnackbar()
|
||||
const {
|
||||
reactionEntities,
|
||||
actions: { createReaction, deleteReaction, loadReactionsBy }
|
||||
} = useReactions()
|
||||
|
||||
const checkReaction = (reactionKind: ReactionKind) =>
|
||||
Object.values(reactionEntities).some(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.createdBy.slug === userSlug() &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.replyTo === props.comment.id
|
||||
)
|
||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
||||
const canVote = createMemo(() => userSlug() !== props.comment.createdBy.slug)
|
||||
|
||||
const shoutRatingReactions = createMemo(() =>
|
||||
Object.values(reactionEntities).filter(
|
||||
(r) =>
|
||||
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.replyTo === props.comment.id
|
||||
)
|
||||
)
|
||||
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
|
||||
const reactionToDelete = Object.values(reactionEntities).find(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.createdBy.slug === userSlug() &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.replyTo === 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,
|
||||
replyTo: props.comment.id
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
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() || !userSlug()}
|
||||
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"
|
||||
>
|
||||
<ul class={clsx('nodash')}>
|
||||
<For each={shoutRatingReactions()}>
|
||||
{(reaction) => (
|
||||
<li>
|
||||
{reaction.kind === ReactionKind.Like ? <>+1</> : <>−1</>} {reaction.createdBy.name}
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</Popup>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!canVote() || !userSlug()}
|
||||
onClick={() => handleRatingChange(false)}
|
||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
||||
[styles.voted]: isDownvoted()
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -6,7 +6,6 @@ import { clsx } from 'clsx'
|
|||
import styles from './Settings.module.scss'
|
||||
import { useProfileForm } from '../../context/profile'
|
||||
import validateUrl from '../../utils/validateUrl'
|
||||
|
||||
import { createFileUploader, UploadFile } from '@solid-primitives/upload'
|
||||
import { Loading } from '../../components/_shared/Loading'
|
||||
import { useSession } from '../../context/session'
|
||||
|
|
3
src/utils/clone.ts
Normal file
3
src/utils/clone.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// debug nested objects console.log('message', clone(obj))
|
||||
|
||||
export const clone = <T>(obj: T): T => JSON.parse(JSON.stringify(obj))
|
Loading…
Reference in New Issue
Block a user