Merge pull request #403 from Discours/hotfix/topicSubscibtionsFix
Fix topic subscriptions status
This commit is contained in:
commit
b3e3068a8d
|
@ -16,13 +16,10 @@ import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Userpic } from '../Userpic'
|
import { Userpic } from '../Userpic'
|
||||||
|
|
||||||
|
import { FollowedInfo } from '../../../pages/types'
|
||||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||||
import styles from './AuthorBadge.module.scss'
|
import styles from './AuthorBadge.module.scss'
|
||||||
|
|
||||||
type FollowedInfo = {
|
|
||||||
value?: boolean
|
|
||||||
loaded?: boolean
|
|
||||||
}
|
|
||||||
type Props = {
|
type Props = {
|
||||||
author: Author
|
author: Author
|
||||||
minimizeSubscribeButton?: boolean
|
minimizeSubscribeButton?: boolean
|
||||||
|
|
|
@ -308,7 +308,13 @@ export const AuthorCard = (props: Props) => {
|
||||||
author={subscription}
|
author={subscription}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TopicBadge topic={subscription} />
|
<TopicBadge
|
||||||
|
isFollowed={{
|
||||||
|
loaded: Boolean(authorSubs()),
|
||||||
|
value: isOwnerSubscribed(subscription.id),
|
||||||
|
}}
|
||||||
|
topic={subscription}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</For>
|
</For>
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
.TopicBadge {
|
.TopicBadge {
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
align-items: flex-start;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: .8rem;
|
||||||
|
}
|
||||||
.basicInfo {
|
.basicInfo {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
|
@ -78,3 +81,34 @@
|
||||||
width: 9em;
|
width: 9em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
|
color: var(--secondary-color);
|
||||||
|
display: flex;
|
||||||
|
margin: 0 0 1em;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsItem {
|
||||||
|
@include font-size(1.4rem);
|
||||||
|
|
||||||
|
margin-right: 1.6rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.followers {
|
||||||
|
word-break: keep-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, createEffect, createSignal } from 'solid-js'
|
import { Show, createEffect, createSignal, on } from 'solid-js'
|
||||||
|
|
||||||
import { useFollowing } from '../../../context/following'
|
import { useFollowing } from '../../../context/following'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -11,11 +11,14 @@ import { getImageUrl } from '../../../utils/getImageUrl'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { CheckButton } from '../../_shared/CheckButton'
|
import { CheckButton } from '../../_shared/CheckButton'
|
||||||
|
|
||||||
|
import { FollowedInfo } from '../../../pages/types'
|
||||||
import styles from './TopicBadge.module.scss'
|
import styles from './TopicBadge.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
topic: Topic
|
topic: Topic
|
||||||
minimizeSubscribeButton?: boolean
|
minimizeSubscribeButton?: boolean
|
||||||
|
isFollowed?: FollowedInfo
|
||||||
|
showStat?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopicBadge = (props: Props) => {
|
export const TopicBadge = (props: Props) => {
|
||||||
|
@ -24,12 +27,12 @@ export const TopicBadge = (props: Props) => {
|
||||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||||
const { requireAuthentication } = useSession()
|
const { requireAuthentication } = useSession()
|
||||||
const { setFollowing, loading: subLoading } = useFollowing()
|
const { setFollowing, loading: subLoading } = useFollowing()
|
||||||
const [followed, setFollowed] = createSignal()
|
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||||
|
|
||||||
const handleFollowClick = () => {
|
const handleFollowClick = () => {
|
||||||
const value = !followed()
|
const value = !isFollowed()
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
setFollowed(value)
|
setIsFollowed(value)
|
||||||
setFollowing(FollowingEntity.Topic, props.topic.slug, value)
|
setFollowing(FollowingEntity.Topic, props.topic.slug, value)
|
||||||
}, 'subscribe')
|
}, 'subscribe')
|
||||||
}
|
}
|
||||||
|
@ -38,67 +41,85 @@ export const TopicBadge = (props: Props) => {
|
||||||
setIsMobileView(!mediaMatches.sm)
|
setIsMobileView(!mediaMatches.sm)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => props.isFollowed,
|
||||||
|
() => {
|
||||||
|
setIsFollowed(props.isFollowed.value)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const title = () =>
|
const title = () =>
|
||||||
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.TopicBadge}>
|
<div class={styles.TopicBadge}>
|
||||||
<div class={styles.basicInfo}>
|
<div class={styles.content}>
|
||||||
<a
|
<div class={styles.basicInfo}>
|
||||||
href={`/topic/${props.topic.slug}`}
|
<a
|
||||||
class={clsx(styles.picture, {
|
href={`/topic/${props.topic.slug}`}
|
||||||
[styles.withImage]: props.topic.pic,
|
class={clsx(styles.picture, {
|
||||||
[styles.smallSize]: isMobileView(),
|
[styles.withImage]: props.topic.pic,
|
||||||
})}
|
[styles.smallSize]: isMobileView(),
|
||||||
style={
|
})}
|
||||||
props.topic.pic && {
|
style={
|
||||||
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
|
props.topic.pic && {
|
||||||
|
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
||||||
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
<span class={styles.title}>{title()}</span>
|
||||||
<span class={styles.title}>{title()}</span>
|
<Show
|
||||||
|
when={props.topic.body}
|
||||||
|
fallback={
|
||||||
|
<div class={styles.description}>
|
||||||
|
{t('PublicationsWithCount', { count: props.topic.stat.shouts ?? 0 })}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
|
||||||
|
</Show>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={styles.actions}>
|
||||||
<Show
|
<Show
|
||||||
when={props.topic.body}
|
when={!props.minimizeSubscribeButton}
|
||||||
fallback={
|
fallback={
|
||||||
<div class={styles.description}>
|
<CheckButton text={t('Follow')} checked={Boolean(isFollowed())} onClick={handleFollowClick} />
|
||||||
{t('PublicationsWithCount', { count: props.topic.stat.shouts ?? 0 })}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
|
<Show
|
||||||
</Show>
|
when={isFollowed()}
|
||||||
</a>
|
fallback={
|
||||||
</div>
|
<Button
|
||||||
|
variant="primary"
|
||||||
<div class={styles.actions}>
|
size="S"
|
||||||
<Show
|
value={subLoading() ? t('subscribing...') : t('Subscribe')}
|
||||||
when={!props.minimizeSubscribeButton}
|
onClick={handleFollowClick}
|
||||||
fallback={
|
class={styles.subscribeButton}
|
||||||
<CheckButton text={t('Follow')} checked={Boolean(followed())} onClick={handleFollowClick} />
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Show
|
|
||||||
when={followed()}
|
|
||||||
fallback={
|
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
|
||||||
size="S"
|
|
||||||
value={subLoading() ? t('subscribing...') : t('Subscribe')}
|
|
||||||
onClick={handleFollowClick}
|
onClick={handleFollowClick}
|
||||||
|
variant="bordered"
|
||||||
|
size="S"
|
||||||
|
value={t('Following')}
|
||||||
class={styles.subscribeButton}
|
class={styles.subscribeButton}
|
||||||
/>
|
/>
|
||||||
}
|
</Show>
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={handleFollowClick}
|
|
||||||
variant="bordered"
|
|
||||||
size="S"
|
|
||||||
value={t('Following')}
|
|
||||||
class={styles.subscribeButton}
|
|
||||||
/>
|
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={styles.stats}>
|
||||||
|
<span class={styles.statsItem}>{t('shoutsWithCount', { count: props.topic?.stat?.shouts })}</span>
|
||||||
|
<span class={styles.statsItem}>{t('authorsWithCount', { count: props.topic?.stat?.authors })}</span>
|
||||||
|
<span class={styles.statsItem}>
|
||||||
|
{t('followersWithCount', { count: props.topic?.stat?.followers })}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,45 +32,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats {
|
|
||||||
@include font-size(1.7rem);
|
|
||||||
|
|
||||||
color: #9fa1a7;
|
|
||||||
display: flex;
|
|
||||||
margin: 0 0 1em;
|
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.statsItem {
|
|
||||||
@include font-size(1.5rem);
|
|
||||||
|
|
||||||
margin-right: 1.6rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.compact {
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.followers {
|
|
||||||
word-break: keep-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.button {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadMoreContainer {
|
.loadMoreContainer {
|
||||||
margin-top: 48px;
|
margin-top: 48px;
|
||||||
text-align: center;
|
text-align: center;
|
|
@ -1,21 +1,22 @@
|
||||||
import type { Topic } from '../../graphql/schema/core.gen'
|
import type { Topic } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { Meta } from '@solidjs/meta'
|
import { Meta } from '@solidjs/meta'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { For, Show, createEffect, createMemo, createSignal } from 'solid-js'
|
import { For, Show, createEffect, createMemo, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useFollowing } from '../../context/following'
|
import { useFollowing } from '../../../context/following'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
|
import { setTopicsSort, useTopicsStore } from '../../../stores/zine/topics'
|
||||||
import { capitalize } from '../../utils/capitalize'
|
import { capitalize } from '../../../utils/capitalize'
|
||||||
import { dummyFilter } from '../../utils/dummyFilter'
|
import { dummyFilter } from '../../../utils/dummyFilter'
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||||
import { scrollHandler } from '../../utils/scroll'
|
import { scrollHandler } from '../../../utils/scroll'
|
||||||
import { TopicCard } from '../Topic/Card'
|
import { TopicCard } from '../../Topic/Card'
|
||||||
import { Loading } from '../_shared/Loading'
|
import { Loading } from '../../_shared/Loading'
|
||||||
import { SearchField } from '../_shared/SearchField'
|
import { SearchField } from '../../_shared/SearchField'
|
||||||
|
|
||||||
|
import { TopicBadge } from '../../Topic/TopicBadge'
|
||||||
import styles from './AllTopics.module.scss'
|
import styles from './AllTopics.module.scss'
|
||||||
|
|
||||||
type AllTopicsPageSearchParams = {
|
type AllTopicsPageSearchParams = {
|
||||||
|
@ -29,7 +30,7 @@ type Props = {
|
||||||
|
|
||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
export const AllTopicsView = (props: Props) => {
|
export const AllTopics = (props: Props) => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
const { searchParams, changeSearchParams } = useRouter<AllTopicsPageSearchParams>()
|
const { searchParams, changeSearchParams } = useRouter<AllTopicsPageSearchParams>()
|
||||||
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
||||||
|
@ -41,8 +42,6 @@ export const AllTopicsView = (props: Props) => {
|
||||||
sortBy: searchParams().by || 'shouts',
|
sortBy: searchParams().by || 'shouts',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { subscriptions } = useFollowing()
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!searchParams().by) {
|
if (!searchParams().by) {
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
|
@ -76,7 +75,7 @@ export const AllTopicsView = (props: Props) => {
|
||||||
return keys
|
return keys
|
||||||
})
|
})
|
||||||
|
|
||||||
const subscribed = (topicSlug: string) => subscriptions.topics.some((topic) => topic.slug === topicSlug)
|
const { isOwnerSubscribed } = useFollowing()
|
||||||
|
|
||||||
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
|
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
|
||||||
const [searchQuery, setSearchQuery] = createSignal('')
|
const [searchQuery, setSearchQuery] = createSignal('')
|
||||||
|
@ -186,28 +185,18 @@ export const AllTopicsView = (props: Props) => {
|
||||||
|
|
||||||
<Show when={searchParams().by && searchParams().by !== 'title'}>
|
<Show when={searchParams().by && searchParams().by !== 'title'}>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-20 col-xl-18">
|
<div class="col-lg-20 col-xl-18 py-4">
|
||||||
<For each={filteredResults().slice(0, limit())}>
|
<For each={filteredResults().slice(0, limit())}>
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<>
|
<>
|
||||||
<TopicCard
|
<TopicBadge
|
||||||
topic={topic}
|
topic={topic}
|
||||||
compact={false}
|
isFollowed={{
|
||||||
subscribed={subscribed(topic.slug)}
|
loaded: filteredResults().length > 0,
|
||||||
showPublications={true}
|
value: isOwnerSubscribed(topic.slug),
|
||||||
showDescription={true}
|
}}
|
||||||
|
showStat={true}
|
||||||
/>
|
/>
|
||||||
<div class={styles.stats}>
|
|
||||||
<span class={styles.statsItem}>
|
|
||||||
{t('shoutsWithCount', { count: topic.stat.shouts })}
|
|
||||||
</span>
|
|
||||||
<span class={styles.statsItem}>
|
|
||||||
{t('authorsWithCount', { count: topic.stat.authors })}
|
|
||||||
</span>
|
|
||||||
<span class={styles.statsItem}>
|
|
||||||
{t('followersWithCount', { count: topic.stat.followers })}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
1
src/components/Views/AllTopics/index.ts
Normal file
1
src/components/Views/AllTopics/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { AllTopics } from './AllTopics'
|
|
@ -128,7 +128,6 @@ export const AuthorView = (props: Props) => {
|
||||||
const data = await apiClient.getReactionsBy({
|
const data = await apiClient.getReactionsBy({
|
||||||
by: { comment: false, created_by: commenter.id },
|
by: { comment: false, created_by: commenter.id },
|
||||||
})
|
})
|
||||||
console.debug('[components.Author] fetched comments', data)
|
|
||||||
setCommented(data)
|
setCommented(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ interface FollowingContextType {
|
||||||
loadSubscriptions: () => void
|
loadSubscriptions: () => void
|
||||||
follow: (what: FollowingEntity, slug: string) => Promise<void>
|
follow: (what: FollowingEntity, slug: string) => Promise<void>
|
||||||
unfollow: (what: FollowingEntity, slug: string) => Promise<void>
|
unfollow: (what: FollowingEntity, slug: string) => Promise<void>
|
||||||
isOwnerSubscribed: (userId: number) => boolean
|
isOwnerSubscribed: (id: number | string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const FollowingContext = createContext<FollowingContextType>()
|
const FollowingContext = createContext<FollowingContextType>()
|
||||||
|
@ -109,9 +109,11 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOwnerSubscribed = (userId: number) => {
|
const isOwnerSubscribed = (id?: number | string) => {
|
||||||
if (!author()) return
|
if (!author() || !subscriptions) return
|
||||||
return !!subscriptions?.authors?.some((authorEntity) => authorEntity.id === userId)
|
const isAuthorSubscribed = subscriptions.authors?.some((authorEntity) => authorEntity.id === id)
|
||||||
|
const isTopicSubscribed = subscriptions.topics?.some((topicEntity) => topicEntity.slug === id)
|
||||||
|
return !!isAuthorSubscribed || !!isTopicSubscribed
|
||||||
}
|
}
|
||||||
|
|
||||||
const value: FollowingContextType = {
|
const value: FollowingContextType = {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { PageProps } from './types'
|
||||||
|
|
||||||
import { createSignal, onMount } from 'solid-js'
|
import { createSignal, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { AllTopicsView } from '../components/Views/AllTopics'
|
import { AllTopics } from '../components/Views/AllTopics'
|
||||||
import { PageLayout } from '../components/_shared/PageLayout'
|
import { PageLayout } from '../components/_shared/PageLayout'
|
||||||
import { useLocalize } from '../context/localize'
|
import { useLocalize } from '../context/localize'
|
||||||
import { loadAllTopics } from '../stores/zine/topics'
|
import { loadAllTopics } from '../stores/zine/topics'
|
||||||
|
@ -23,7 +23,7 @@ export const AllTopicsPage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout title={t('Themes and plots')}>
|
<PageLayout title={t('Themes and plots')}>
|
||||||
<AllTopicsView isLoaded={isLoaded()} topics={props.allTopics} />
|
<AllTopics isLoaded={isLoaded()} topics={props.allTopics} />
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,4 +50,9 @@ export type UploadedFile = {
|
||||||
originalFilename?: string
|
originalFilename?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FollowedInfo = {
|
||||||
|
value?: boolean
|
||||||
|
loaded?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type SubscriptionFilter = 'all' | 'authors' | 'topics' | 'communities'
|
export type SubscriptionFilter = 'all' | 'authors' | 'topics' | 'communities'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user