diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 7d25a6bf..2a39073f 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -2,16 +2,14 @@ import { capitalize, formatDate } from '../../utils' import './Full.scss' import { Icon } from '../_shared/Icon' 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 { ReactionKind } from '../../graphql/types.gen' - import MD from './MD' import { SharePopup } from './SharePopup' import { getDescription } from '../../utils/meta' import stylesHeader from '../Nav/Header.module.scss' import styles from '../../styles/Article.module.scss' -import { RatingControl } from './RatingControl' +import { ShoutRatingControl } from './ShoutRatingControl' import { clsx } from 'clsx' import { CommentsTree } from './CommentsTree' import { useSession } from '../../context/session' @@ -20,10 +18,8 @@ import Slider from '../_shared/Slider' import { getPagePath } from '@nanostores/router' import { router } from '../../stores/router' import { useReactions } from '../../context/reactions' -import { loadShout } from '../../stores/zine/articles' import { Title } from '@solidjs/meta' import { useLocalize } from '../../context/localize' -import { checkReaction } from '../../utils/checkReaction' interface ArticleProps { article: Shout @@ -60,7 +56,7 @@ const MediaView = (props: { media: MediaItem; kind: Shout['layout'] }) => { export const FullArticle = (props: ArticleProps) => { const { t } = useLocalize() - const { userSlug, session } = useSession() + const { userSlug, isAuthenticated } = useSession() const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false) const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt))) @@ -91,7 +87,7 @@ export const FullArticle = (props: ArticleProps) => { 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) => { // TODO: implement bookmark clicked @@ -106,68 +102,9 @@ export const FullArticle = (props: ArticleProps) => { }) const { - reactionEntities, - actions: { loadReactionsBy, createReaction, deleteReaction } + actions: { loadReactionsBy } } = 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 ( <> {props.article.title} @@ -248,14 +185,7 @@ export const FullArticle = (props: ArticleProps) => {
- handleRatingChange(true)} - onDownvote={() => handleRatingChange(false)} - isUpvoted={isUpvoted()} - isDownvoted={isDownvoted()} - /> +
@@ -299,7 +229,7 @@ export const FullArticle = (props: ArticleProps) => {
- + diff --git a/src/components/Article/RatingControl.tsx b/src/components/Article/RatingControl.tsx deleted file mode 100644 index bfed81bb..00000000 --- a/src/components/Article/RatingControl.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import styles from './RatingControl.module.scss' -import { clsx } from 'clsx' - -interface RatingControlProps { - rating?: number - class?: string - onUpvote: () => Promise | void - onDownvote: () => Promise | void - isUpvoted: boolean - isDownvoted: boolean -} - -export const RatingControl = (props: RatingControlProps) => { - return ( -
- - {props?.rating || ''} - -
- ) -} diff --git a/src/components/Article/ShoutRatingControl.module.scss b/src/components/Article/ShoutRatingControl.module.scss new file mode 100644 index 00000000..bd8a218a --- /dev/null +++ b/src/components/Article/ShoutRatingControl.module.scss @@ -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; + } +} diff --git a/src/components/Article/ShoutRatingControl.tsx b/src/components/Article/ShoutRatingControl.tsx new file mode 100644 index 00000000..20a76b1c --- /dev/null +++ b/src/components/Article/ShoutRatingControl.tsx @@ -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 ( +
+ + + {props.shout.stat.rating}} variant="tiny"> +
    + + {(reaction) => ( +
  • + {reaction.kind === ReactionKind.Like ? <>+1 : <>−1} {reaction.createdBy.name} +
  • + )} +
    +
+
+ + +
+ ) +} diff --git a/src/components/Article/RatingControl.module.scss b/src/components/Author/AuthorRatingControl.module.scss similarity index 100% rename from src/components/Article/RatingControl.module.scss rename to src/components/Author/AuthorRatingControl.module.scss diff --git a/src/components/Author/AuthorRatingControl.tsx b/src/components/Author/AuthorRatingControl.tsx new file mode 100644 index 00000000..d1461d83 --- /dev/null +++ b/src/components/Author/AuthorRatingControl.tsx @@ -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 ( +
+ + {/*TODO*/} + {123} + +
+ ) +} diff --git a/src/components/Feed/Card.tsx b/src/components/Feed/Card.tsx index d6522466..6ccc1fbb 100644 --- a/src/components/Feed/Card.tsx +++ b/src/components/Feed/Card.tsx @@ -6,17 +6,13 @@ import { Icon } from '../_shared/Icon' import styles from './Card.module.scss' import { clsx } from 'clsx' import { CardTopic } from './CardTopic' -import { RatingControl } from '../Article/RatingControl' +import { ShoutRatingControl } from '../Article/ShoutRatingControl' import { getShareUrl, SharePopup } from '../Article/SharePopup' import stylesHeader from '../Nav/Header.module.scss' import { getDescription } from '../../utils/meta' import { FeedArticlePopup } from './FeedArticlePopup' import { useLocalize } from '../../context/localize' -import { ReactionKind } from '../../graphql/types.gen' -import { loadShout } from '../../stores/zine/articles' import { useReactions } from '../../context/reactions' -import { checkReaction } from '../../utils/checkReaction' -import { useSession } from '../../context/session' interface ArticleCardProps { settings?: { @@ -66,13 +62,6 @@ const getTitleAndSubtitle = (article: Shout): { title: string; subtitle: string export const ArticleCard = (props: ArticleCardProps) => { const { t, lang } = useLocalize() - const { userSlug } = useSession() - - const { - reactionEntities, - actions: { createReaction, deleteReaction, loadReactionsBy } - } = useReactions() - const mainTopic = props.article.topics.find((articleTopic) => articleTopic.slug === props.article.mainTopic) || props.article.topics[0] @@ -85,57 +74,7 @@ export const ArticleCard = (props: ArticleCardProps) => { const { title, subtitle } = getTitleAndSubtitle(props.article) - const { cover, layout, slug, authors, stat, body, id } = 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() - } + const { cover, layout, slug, authors, stat, body } = props.article return (
{
- handleRatingChange(true)} - onDownvote={() => handleRatingChange(false)} - isUpvoted={isUpvoted()} - isDownvoted={isDownvoted()} - /> +
diff --git a/src/components/Feed/CardTopic.tsx b/src/components/Feed/CardTopic.tsx index 617d10d9..0b51c3f0 100644 --- a/src/components/Feed/CardTopic.tsx +++ b/src/components/Feed/CardTopic.tsx @@ -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 slug: string isFloorImportant?: boolean @@ -9,12 +12,11 @@ interface CardTopicProps { export const CardTopic = (props: CardTopicProps) => { return ( ) } diff --git a/src/components/Feed/FeedArticlePopup.tsx b/src/components/Feed/FeedArticlePopup.tsx index 3d4872ed..22851e26 100644 --- a/src/components/Feed/FeedArticlePopup.tsx +++ b/src/components/Feed/FeedArticlePopup.tsx @@ -10,12 +10,6 @@ type FeedArticlePopupProps = { description: string } & Omit -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) => { const { t } = useLocalize() return ( diff --git a/src/components/Nav/ProfilePopup.tsx b/src/components/Nav/ProfilePopup.tsx index 8aced53b..9dd0e135 100644 --- a/src/components/Nav/ProfilePopup.tsx +++ b/src/components/Nav/ProfilePopup.tsx @@ -14,15 +14,13 @@ export const ProfilePopup = (props: ProfilePopupProps) => { actions: { signOut } } = useSession() - const { t, lang } = useLocalize() + const { t } = useLocalize() return (
  • - - {t('Profile')} - + {t('Profile')}
  • {t('Drafts')} diff --git a/src/components/Views/Article.tsx b/src/components/Views/Article.tsx deleted file mode 100644 index 996fc513..00000000 --- a/src/components/Views/Article.tsx +++ /dev/null @@ -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 -} diff --git a/src/components/Views/Author.tsx b/src/components/Views/Author.tsx index b9f14f3a..2d46b85e 100644 --- a/src/components/Views/Author.tsx +++ b/src/components/Views/Author.tsx @@ -9,7 +9,6 @@ import { loadShouts, useArticlesStore } from '../../stores/zine/articles' import { useRouter } from '../../stores/router' import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' import { splitToPages } from '../../utils/splitToPages' -import { RatingControl } from '../Article/RatingControl' import styles from './Author.module.scss' import stylesArticle from '../../styles/Article.module.scss' import { clsx } from 'clsx' @@ -19,6 +18,7 @@ import { AuthorCard } from '../Author/Card' import { apiClient } from '../../utils/apiClient' import { Comment } from '../Article/Comment' import { useLocalize } from '../../context/localize' +import { AuthorRatingControl } from '../Author/AuthorRatingControl' type AuthorProps = { shouts: Shout[] @@ -138,7 +138,6 @@ export const AuthorView = (props: AuthorProps) => {
@@ -179,7 +178,7 @@ export const AuthorView = (props: AuthorProps) => {
{t('Karma')} - +
diff --git a/src/pages/article.page.tsx b/src/pages/article.page.tsx index c5ffd429..8378bf37 100644 --- a/src/pages/article.page.tsx +++ b/src/pages/article.page.tsx @@ -1,12 +1,12 @@ import { createMemo, onMount, Show } from 'solid-js' import type { Shout } from '../graphql/types.gen' import { PageLayout } from '../components/_shared/PageLayout' -import { ArticleView } from '../components/Views/Article' import type { PageProps } from './types' import { loadShout, useArticlesStore } from '../stores/zine/articles' import { useRouter } from '../stores/router' import { Loading } from '../components/_shared/Loading' import { ReactionsProvider } from '../context/reactions' +import { FullArticle } from '../components/Article/FullArticle' export const ArticlePage = (props: PageProps) => { const shouts = props.article ? [props.article] : [] @@ -50,7 +50,7 @@ export const ArticlePage = (props: PageProps) => { }> - + diff --git a/src/utils/checkReaction.ts b/src/utils/checkReaction.ts deleted file mode 100644 index 19f157ea..00000000 --- a/src/utils/checkReaction.ts +++ /dev/null @@ -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 - )