Merge branch 'rating-fixes' into 'dev'
likes/dislikes refactoring, who voted popup See merge request discoursio/discoursio-webapp!34
This commit is contained in:
commit
27fc91bb87
|
@ -2,16 +2,14 @@ import { capitalize, formatDate } from '../../utils'
|
||||||
import './Full.scss'
|
import './Full.scss'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/Card'
|
||||||
import { createEffect, createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js'
|
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js'
|
||||||
import type { Author, Shout } from '../../graphql/types.gen'
|
import type { Author, Shout } from '../../graphql/types.gen'
|
||||||
import { ReactionKind } from '../../graphql/types.gen'
|
|
||||||
|
|
||||||
import MD from './MD'
|
import MD from './MD'
|
||||||
import { SharePopup } from './SharePopup'
|
import { SharePopup } from './SharePopup'
|
||||||
import { getDescription } from '../../utils/meta'
|
import { getDescription } from '../../utils/meta'
|
||||||
import stylesHeader from '../Nav/Header.module.scss'
|
import stylesHeader from '../Nav/Header.module.scss'
|
||||||
import styles from '../../styles/Article.module.scss'
|
import styles from '../../styles/Article.module.scss'
|
||||||
import { RatingControl } from './RatingControl'
|
import { ShoutRatingControl } from './ShoutRatingControl'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { CommentsTree } from './CommentsTree'
|
import { CommentsTree } from './CommentsTree'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
|
@ -20,10 +18,8 @@ import Slider from '../_shared/Slider'
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { router } from '../../stores/router'
|
import { router } from '../../stores/router'
|
||||||
import { useReactions } from '../../context/reactions'
|
import { useReactions } from '../../context/reactions'
|
||||||
import { loadShout } from '../../stores/zine/articles'
|
|
||||||
import { Title } from '@solidjs/meta'
|
import { Title } from '@solidjs/meta'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { checkReaction } from '../../utils/checkReaction'
|
|
||||||
|
|
||||||
interface ArticleProps {
|
interface ArticleProps {
|
||||||
article: Shout
|
article: Shout
|
||||||
|
@ -60,7 +56,7 @@ const MediaView = (props: { media: MediaItem; kind: Shout['layout'] }) => {
|
||||||
|
|
||||||
export const FullArticle = (props: ArticleProps) => {
|
export const FullArticle = (props: ArticleProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { userSlug, session } = useSession()
|
const { userSlug, isAuthenticated } = useSession()
|
||||||
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
||||||
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
|
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
|
||||||
|
|
||||||
|
@ -91,7 +87,7 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
setIsReactionsLoaded(true)
|
setIsReactionsLoaded(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
const canEdit = () => props.article.authors?.some((a) => a.slug === session()?.user?.slug)
|
const canEdit = () => props.article.authors?.some((a) => a.slug === userSlug())
|
||||||
|
|
||||||
const bookmark = (ev) => {
|
const bookmark = (ev) => {
|
||||||
// TODO: implement bookmark clicked
|
// TODO: implement bookmark clicked
|
||||||
|
@ -106,68 +102,9 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reactionEntities,
|
actions: { loadReactionsBy }
|
||||||
actions: { loadReactionsBy, createReaction, deleteReaction }
|
|
||||||
} = useReactions()
|
} = useReactions()
|
||||||
|
|
||||||
const updateReactions = () => {
|
|
||||||
loadReactionsBy({
|
|
||||||
by: { shout: props.article.slug }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const isUpvoted = createMemo(() =>
|
|
||||||
checkReaction(Object.values(reactionEntities), ReactionKind.Like, userSlug(), props.article.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
const isDownvoted = createMemo(() =>
|
|
||||||
checkReaction(Object.values(reactionEntities), ReactionKind.Dislike, userSlug(), props.article.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
|
||||||
const reactionToDelete = Object.values(reactionEntities).find(
|
|
||||||
(r) =>
|
|
||||||
r.kind === reactionKind &&
|
|
||||||
r.createdBy.slug === userSlug() &&
|
|
||||||
r.shout.id === props.article.id &&
|
|
||||||
!r.replyTo
|
|
||||||
)
|
|
||||||
return deleteReaction(reactionToDelete.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRatingChange = async (isUpvote: boolean) => {
|
|
||||||
if (isUpvote) {
|
|
||||||
if (isUpvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Like)
|
|
||||||
} else if (isDownvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Dislike)
|
|
||||||
} else {
|
|
||||||
await createReaction({
|
|
||||||
kind: ReactionKind.Like,
|
|
||||||
shout: props.article.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDownvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Dislike)
|
|
||||||
} else if (isUpvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Like)
|
|
||||||
} else {
|
|
||||||
await createReaction({
|
|
||||||
kind: ReactionKind.Dislike,
|
|
||||||
shout: props.article.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadShout(props.article.slug)
|
|
||||||
updateReactions()
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
console.log('reactions', reactionEntities)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title>{props.article.title}</Title>
|
<Title>{props.article.title}</Title>
|
||||||
|
@ -248,14 +185,7 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
<div class="col-md-8 shift-content">
|
<div class="col-md-8 shift-content">
|
||||||
<div class={styles.shoutStats}>
|
<div class={styles.shoutStats}>
|
||||||
<div class={styles.shoutStatsItem}>
|
<div class={styles.shoutStatsItem}>
|
||||||
<RatingControl
|
<ShoutRatingControl shout={props.article} class={styles.ratingControl} />
|
||||||
rating={props.article.stat?.rating}
|
|
||||||
class={styles.ratingControl}
|
|
||||||
onUpvote={() => handleRatingChange(true)}
|
|
||||||
onDownvote={() => handleRatingChange(false)}
|
|
||||||
isUpvoted={isUpvoted()}
|
|
||||||
isDownvoted={isDownvoted()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={props.article.stat?.viewed}>
|
<Show when={props.article.stat?.viewed}>
|
||||||
|
@ -299,7 +229,7 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.help}>
|
<div class={styles.help}>
|
||||||
<Show when={session()?.token}>
|
<Show when={isAuthenticated()}>
|
||||||
<button class="button">{t('Cooperate')}</button>
|
<button class="button">{t('Cooperate')}</button>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={canEdit()}>
|
<Show when={canEdit()}>
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import styles from './RatingControl.module.scss'
|
|
||||||
import { clsx } from 'clsx'
|
|
||||||
|
|
||||||
interface RatingControlProps {
|
|
||||||
rating?: number
|
|
||||||
class?: string
|
|
||||||
onUpvote: () => Promise<void> | void
|
|
||||||
onDownvote: () => Promise<void> | void
|
|
||||||
isUpvoted: boolean
|
|
||||||
isDownvoted: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RatingControl = (props: RatingControlProps) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={clsx(styles.rating, props.class, {
|
|
||||||
[styles.isUpvoted]: props.isUpvoted,
|
|
||||||
[styles.isDownvoted]: props.isDownvoted
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<button class={clsx(styles.ratingControl, styles.downvoteButton)} onClick={props.onDownvote}>
|
|
||||||
−
|
|
||||||
</button>
|
|
||||||
<span class={styles.ratingValue}>{props?.rating || ''}</span>
|
|
||||||
<button class={clsx(styles.ratingControl, styles.upvoteButton)} onClick={props.onUpvote}>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
42
src/components/Article/ShoutRatingControl.module.scss
Normal file
42
src/components/Article/ShoutRatingControl.module.scss
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
.rating {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&.isDownvoted .downvoteButton,
|
||||||
|
&.isUpvoted .upvoteButton {
|
||||||
|
background: #000;
|
||||||
|
border-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratingValue {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 4px;
|
||||||
|
padding: 0 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratingControl {
|
||||||
|
align-items: center;
|
||||||
|
border: 2px solid;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 0.9em;
|
||||||
|
line-height: 0;
|
||||||
|
font-size: 1.6em;
|
||||||
|
padding: 0;
|
||||||
|
width: 0.9em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #000;
|
||||||
|
border-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
108
src/components/Article/ShoutRatingControl.tsx
Normal file
108
src/components/Article/ShoutRatingControl.tsx
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import styles from './ShoutRatingControl.module.scss'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { createMemo, For, Match, Switch } from 'solid-js'
|
||||||
|
import { Author, ReactionKind, Shout } from '../../graphql/types.gen'
|
||||||
|
import { loadShout } from '../../stores/zine/articles'
|
||||||
|
import { useSession } from '../../context/session'
|
||||||
|
import { useReactions } from '../../context/reactions'
|
||||||
|
import { Button } from '../_shared/Button'
|
||||||
|
import Userpic from '../Author/Userpic'
|
||||||
|
import { AuthorCard } from '../Author/Card'
|
||||||
|
import { Popup } from '../_shared/Popup'
|
||||||
|
|
||||||
|
interface ShoutRatingControlProps {
|
||||||
|
shout: Shout
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
|
const { userSlug } = useSession()
|
||||||
|
|
||||||
|
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.shout.id &&
|
||||||
|
!r.replyTo
|
||||||
|
)
|
||||||
|
|
||||||
|
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||||
|
|
||||||
|
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
||||||
|
|
||||||
|
const shoutRatingReactions = createMemo(() =>
|
||||||
|
Object.values(reactionEntities).filter(
|
||||||
|
(r) => [ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) && r.shout.id === props.shout.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
||||||
|
const reactionToDelete = Object.values(reactionEntities).find(
|
||||||
|
(r) =>
|
||||||
|
r.kind === reactionKind &&
|
||||||
|
r.createdBy.slug === userSlug() &&
|
||||||
|
r.shout.id === props.shout.id &&
|
||||||
|
!r.replyTo
|
||||||
|
)
|
||||||
|
return deleteReaction(reactionToDelete.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRatingChange = async (isUpvote: boolean) => {
|
||||||
|
if (isUpvoted()) {
|
||||||
|
await deleteShoutReaction(ReactionKind.Like)
|
||||||
|
} else if (isDownvoted()) {
|
||||||
|
await deleteShoutReaction(ReactionKind.Dislike)
|
||||||
|
} else {
|
||||||
|
await createReaction({
|
||||||
|
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||||
|
shout: props.shout.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadShout(props.shout.slug)
|
||||||
|
loadReactionsBy({
|
||||||
|
by: { shout: props.shout.slug }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(styles.rating, props.class, {
|
||||||
|
[styles.isUpvoted]: isUpvoted(),
|
||||||
|
[styles.isDownvoted]: isDownvoted()
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class={clsx(styles.ratingControl, styles.downvoteButton)}
|
||||||
|
onClick={() => handleRatingChange(false)}
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Popup trigger={<span class={styles.ratingValue}>{props.shout.stat.rating}</span>} 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
|
||||||
|
class={clsx(styles.ratingControl, styles.upvoteButton)}
|
||||||
|
onClick={() => handleRatingChange(true)}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
41
src/components/Author/AuthorRatingControl.tsx
Normal file
41
src/components/Author/AuthorRatingControl.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import styles from './AuthorRatingControl.module.scss'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import type { Author } from '../../graphql/types.gen'
|
||||||
|
|
||||||
|
interface AuthorRatingControlProps {
|
||||||
|
author: Author
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AuthorRatingControl = (props: AuthorRatingControlProps) => {
|
||||||
|
const isUpvoted = false
|
||||||
|
const isDownvoted = false
|
||||||
|
|
||||||
|
const handleRatingChange = (isUpvote: boolean) => {
|
||||||
|
console.log('handleRatingChange', { isUpvote })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(styles.rating, props.class, {
|
||||||
|
[styles.isUpvoted]: isUpvoted,
|
||||||
|
[styles.isDownvoted]: isDownvoted
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class={clsx(styles.ratingControl, styles.downvoteButton)}
|
||||||
|
onClick={() => handleRatingChange(false)}
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
{/*TODO*/}
|
||||||
|
<span class={styles.ratingValue}>{123}</span>
|
||||||
|
<button
|
||||||
|
class={clsx(styles.ratingControl, styles.upvoteButton)}
|
||||||
|
onClick={() => handleRatingChange(true)}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -6,17 +6,13 @@ import { Icon } from '../_shared/Icon'
|
||||||
import styles from './Card.module.scss'
|
import styles from './Card.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { CardTopic } from './CardTopic'
|
import { CardTopic } from './CardTopic'
|
||||||
import { RatingControl } from '../Article/RatingControl'
|
import { ShoutRatingControl } from '../Article/ShoutRatingControl'
|
||||||
import { getShareUrl, SharePopup } from '../Article/SharePopup'
|
import { getShareUrl, SharePopup } from '../Article/SharePopup'
|
||||||
import stylesHeader from '../Nav/Header.module.scss'
|
import stylesHeader from '../Nav/Header.module.scss'
|
||||||
import { getDescription } from '../../utils/meta'
|
import { getDescription } from '../../utils/meta'
|
||||||
import { FeedArticlePopup } from './FeedArticlePopup'
|
import { FeedArticlePopup } from './FeedArticlePopup'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { ReactionKind } from '../../graphql/types.gen'
|
|
||||||
import { loadShout } from '../../stores/zine/articles'
|
|
||||||
import { useReactions } from '../../context/reactions'
|
import { useReactions } from '../../context/reactions'
|
||||||
import { checkReaction } from '../../utils/checkReaction'
|
|
||||||
import { useSession } from '../../context/session'
|
|
||||||
|
|
||||||
interface ArticleCardProps {
|
interface ArticleCardProps {
|
||||||
settings?: {
|
settings?: {
|
||||||
|
@ -66,13 +62,6 @@ const getTitleAndSubtitle = (article: Shout): { title: string; subtitle: string
|
||||||
export const ArticleCard = (props: ArticleCardProps) => {
|
export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
|
|
||||||
const { userSlug } = useSession()
|
|
||||||
|
|
||||||
const {
|
|
||||||
reactionEntities,
|
|
||||||
actions: { createReaction, deleteReaction, loadReactionsBy }
|
|
||||||
} = useReactions()
|
|
||||||
|
|
||||||
const mainTopic =
|
const mainTopic =
|
||||||
props.article.topics.find((articleTopic) => articleTopic.slug === props.article.mainTopic) ||
|
props.article.topics.find((articleTopic) => articleTopic.slug === props.article.mainTopic) ||
|
||||||
props.article.topics[0]
|
props.article.topics[0]
|
||||||
|
@ -85,57 +74,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
|
|
||||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||||
|
|
||||||
const { cover, layout, slug, authors, stat, body, id } = props.article
|
const { cover, layout, slug, authors, stat, body } = props.article
|
||||||
|
|
||||||
const updateReactions = () => {
|
|
||||||
loadReactionsBy({
|
|
||||||
by: { shout: slug }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const isUpvoted = createMemo(() =>
|
|
||||||
checkReaction(Object.values(reactionEntities), ReactionKind.Like, userSlug(), id)
|
|
||||||
)
|
|
||||||
|
|
||||||
const isDownvoted = createMemo(() =>
|
|
||||||
checkReaction(Object.values(reactionEntities), ReactionKind.Dislike, userSlug(), id)
|
|
||||||
)
|
|
||||||
|
|
||||||
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
|
||||||
const reactionToDelete = Object.values(reactionEntities).find(
|
|
||||||
(r) => r.kind === reactionKind && r.createdBy.slug === userSlug() && r.shout.id === id && !r.replyTo
|
|
||||||
)
|
|
||||||
return deleteReaction(reactionToDelete.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRatingChange = async (isUpvote: boolean) => {
|
|
||||||
if (isUpvote) {
|
|
||||||
if (isUpvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Like)
|
|
||||||
} else if (isDownvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Dislike)
|
|
||||||
} else {
|
|
||||||
await createReaction({
|
|
||||||
kind: ReactionKind.Like,
|
|
||||||
shout: id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isDownvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Dislike)
|
|
||||||
} else if (isUpvoted()) {
|
|
||||||
await deleteShoutReaction(ReactionKind.Like)
|
|
||||||
} else {
|
|
||||||
await createReaction({
|
|
||||||
kind: ReactionKind.Dislike,
|
|
||||||
shout: id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadShout(slug)
|
|
||||||
updateReactions()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
|
@ -227,14 +166,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
<Show when={props.settings?.isFeedMode}>
|
<Show when={props.settings?.isFeedMode}>
|
||||||
<section class={styles.shoutCardDetails}>
|
<section class={styles.shoutCardDetails}>
|
||||||
<div class={styles.shoutCardDetailsContent}>
|
<div class={styles.shoutCardDetailsContent}>
|
||||||
<RatingControl
|
<ShoutRatingControl shout={props.article} class={styles.shoutCardDetailsItem} />
|
||||||
rating={stat.rating}
|
|
||||||
class={styles.shoutCardDetailsItem}
|
|
||||||
onUpvote={() => handleRatingChange(true)}
|
|
||||||
onDownvote={() => handleRatingChange(false)}
|
|
||||||
isUpvoted={isUpvoted()}
|
|
||||||
isDownvoted={isDownvoted()}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class={clsx(styles.shoutCardDetailsItem, styles.shoutCardDetailsViewed)}>
|
<div class={clsx(styles.shoutCardDetailsItem, styles.shoutCardDetailsViewed)}>
|
||||||
<Icon name="eye" class={clsx(styles.icon, styles.feedControlIcon)} />
|
<Icon name="eye" class={clsx(styles.icon, styles.feedControlIcon)} />
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import style from './CardTopic.module.scss'
|
import { clsx } from 'clsx'
|
||||||
|
import { getPagePath } from '@nanostores/router'
|
||||||
|
import { router } from '../../stores/router'
|
||||||
|
import styles from './CardTopic.module.scss'
|
||||||
|
|
||||||
interface CardTopicProps {
|
type CardTopicProps = {
|
||||||
title: string
|
title: string
|
||||||
slug: string
|
slug: string
|
||||||
isFloorImportant?: boolean
|
isFloorImportant?: boolean
|
||||||
|
@ -9,12 +12,11 @@ interface CardTopicProps {
|
||||||
export const CardTopic = (props: CardTopicProps) => {
|
export const CardTopic = (props: CardTopicProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={style.shoutTopic}
|
class={clsx(styles.shoutTopic, {
|
||||||
classList={{
|
[styles.shoutTopicFloorImportant]: props.isFloorImportant
|
||||||
[style.shoutTopicFloorImportant]: props.isFloorImportant
|
})}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<a href={`/topic/${props.slug}`}>{props.title}</a>
|
<a href={getPagePath(router, 'topic', { slug: props.slug })}>{props.title}</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,6 @@ type FeedArticlePopupProps = {
|
||||||
description: string
|
description: string
|
||||||
} & Omit<PopupProps, 'children'>
|
} & Omit<PopupProps, 'children'>
|
||||||
|
|
||||||
export const getShareUrl = (params: { pathname?: string } = {}) => {
|
|
||||||
if (typeof location === 'undefined') return ''
|
|
||||||
const pathname = params.pathname ?? location.pathname
|
|
||||||
return location.origin + pathname
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
|
export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -14,15 +14,13 @@ export const ProfilePopup = (props: ProfilePopupProps) => {
|
||||||
actions: { signOut }
|
actions: { signOut }
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const { t, lang } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popup {...props} horizontalAnchor="right" variant="bordered">
|
<Popup {...props} horizontalAnchor="right" variant="bordered">
|
||||||
<ul class="nodash">
|
<ul class="nodash">
|
||||||
<li>
|
<li>
|
||||||
<a href={getPagePath(router, 'author', { slug: userSlug(), lang: lang() } as never)}>
|
<a href={getPagePath(router, 'author', { slug: userSlug() })}>{t('Profile')}</a>
|
||||||
{t('Profile')}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#">{t('Drafts')}</a>
|
<a href="#">{t('Drafts')}</a>
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { FullArticle } from '../Article/FullArticle'
|
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
|
||||||
|
|
||||||
interface ArticlePageProps {
|
|
||||||
article: Shout
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ArticleView = (props: ArticlePageProps) => {
|
|
||||||
return <FullArticle article={props.article} />
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||||
import { splitToPages } from '../../utils/splitToPages'
|
import { splitToPages } from '../../utils/splitToPages'
|
||||||
import { RatingControl } from '../Article/RatingControl'
|
|
||||||
import styles from './Author.module.scss'
|
import styles from './Author.module.scss'
|
||||||
import stylesArticle from '../../styles/Article.module.scss'
|
import stylesArticle from '../../styles/Article.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -19,6 +18,7 @@ import { AuthorCard } from '../Author/Card'
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
import { Comment } from '../Article/Comment'
|
import { Comment } from '../Article/Comment'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
import { AuthorRatingControl } from '../Author/AuthorRatingControl'
|
||||||
|
|
||||||
type AuthorProps = {
|
type AuthorProps = {
|
||||||
shouts: Shout[]
|
shouts: Shout[]
|
||||||
|
@ -138,7 +138,6 @@ export const AuthorView = (props: AuthorProps) => {
|
||||||
</div>
|
</div>
|
||||||
<div class={clsx('col-md-4', styles.additionalControls)}>
|
<div class={clsx('col-md-4', styles.additionalControls)}>
|
||||||
<Popup
|
<Popup
|
||||||
{...props}
|
|
||||||
trigger={
|
trigger={
|
||||||
<div class={styles.subscribers}>
|
<div class={styles.subscribers}>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
@ -179,7 +178,7 @@ export const AuthorView = (props: AuthorProps) => {
|
||||||
|
|
||||||
<div class={styles.ratingContainer}>
|
<div class={styles.ratingContainer}>
|
||||||
{t('Karma')}
|
{t('Karma')}
|
||||||
<RatingControl rating={19} class={styles.ratingControl} />
|
<AuthorRatingControl author={props.author} class={styles.ratingControl} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { createMemo, onMount, Show } from 'solid-js'
|
import { createMemo, onMount, Show } from 'solid-js'
|
||||||
import type { Shout } from '../graphql/types.gen'
|
import type { Shout } from '../graphql/types.gen'
|
||||||
import { PageLayout } from '../components/_shared/PageLayout'
|
import { PageLayout } from '../components/_shared/PageLayout'
|
||||||
import { ArticleView } from '../components/Views/Article'
|
|
||||||
import type { PageProps } from './types'
|
import type { PageProps } from './types'
|
||||||
import { loadShout, useArticlesStore } from '../stores/zine/articles'
|
import { loadShout, useArticlesStore } from '../stores/zine/articles'
|
||||||
import { useRouter } from '../stores/router'
|
import { useRouter } from '../stores/router'
|
||||||
import { Loading } from '../components/_shared/Loading'
|
import { Loading } from '../components/_shared/Loading'
|
||||||
import { ReactionsProvider } from '../context/reactions'
|
import { ReactionsProvider } from '../context/reactions'
|
||||||
|
import { FullArticle } from '../components/Article/FullArticle'
|
||||||
|
|
||||||
export const ArticlePage = (props: PageProps) => {
|
export const ArticlePage = (props: PageProps) => {
|
||||||
const shouts = props.article ? [props.article] : []
|
const shouts = props.article ? [props.article] : []
|
||||||
|
@ -50,7 +50,7 @@ export const ArticlePage = (props: PageProps) => {
|
||||||
<PageLayout headerTitle={article()?.title || ''} articleBody={article()?.body} cover={article()?.cover}>
|
<PageLayout headerTitle={article()?.title || ''} articleBody={article()?.body} cover={article()?.cover}>
|
||||||
<ReactionsProvider>
|
<ReactionsProvider>
|
||||||
<Show when={Boolean(article())} fallback={<Loading />}>
|
<Show when={Boolean(article())} fallback={<Loading />}>
|
||||||
<ArticleView article={article()} />
|
<FullArticle article={article()} />
|
||||||
</Show>
|
</Show>
|
||||||
</ReactionsProvider>
|
</ReactionsProvider>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import type { Reaction, ReactionKind } from '../graphql/types.gen'
|
|
||||||
|
|
||||||
export const checkReaction = (
|
|
||||||
reactions: Reaction[],
|
|
||||||
reactionKind: ReactionKind,
|
|
||||||
userSlug: string,
|
|
||||||
shoutId: number
|
|
||||||
) =>
|
|
||||||
reactions.some(
|
|
||||||
(r) => r.kind === reactionKind && r.createdBy.slug === userSlug && r.shout.id === shoutId && !r.replyTo
|
|
||||||
)
|
|
Loading…
Reference in New Issue
Block a user