Merge branch 'dev' of https://github.com/Discours/discoursio-webapp into fix/all-topics-page
This commit is contained in:
commit
7c9ecd1e3a
|
@ -528,5 +528,7 @@
|
|||
"yesterday": "yesterday",
|
||||
"Failed to delete comment": "Failed to delete comment",
|
||||
"It's OK. Just enter your email to receive a link to change your password": "It's OK. Just enter your email to receive a link to change your password",
|
||||
"Restore password": "Restore password"
|
||||
"Restore password": "Restore password",
|
||||
"Subscribing...": "Subscribing...",
|
||||
"Unsubscribing...": "Unsubscribing..."
|
||||
}
|
||||
|
|
|
@ -555,5 +555,7 @@
|
|||
"yesterday": "вчера",
|
||||
"Failed to delete comment": "Не удалось удалить комментарий",
|
||||
"It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
|
||||
"Restore password": "Восстановить пароль"
|
||||
"Restore password": "Восстановить пароль",
|
||||
"Subscribing...": "Подписываем...",
|
||||
"Unsubscribing...": "Отписываем..."
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import { InboxPage } from '../pages/inbox.page'
|
|||
import { HomePage } from '../pages/index.page'
|
||||
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
|
||||
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
|
||||
//TODO: ProfileSubscriptionsPage - garbage code?
|
||||
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
|
||||
import { SearchPage } from '../pages/search.page'
|
||||
import { TopicPage } from '../pages/topic.page'
|
||||
|
|
|
@ -115,8 +115,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionButtonLabelHovered {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,12 @@ import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
|||
import { router, useRouter } from '../../../stores/router'
|
||||
import { translit } from '../../../utils/ru2en'
|
||||
import { isCyrillic } from '../../../utils/translate'
|
||||
import { BadgeSubscribeButton } from '../../_shared/BadgeSubscribeButton'
|
||||
import { Button } from '../../_shared/Button'
|
||||
import { CheckButton } from '../../_shared/CheckButton'
|
||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Userpic } from '../Userpic'
|
||||
|
||||
import { FollowedInfo } from '../../../pages/types'
|
||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||
import styles from './AuthorBadge.module.scss'
|
||||
|
||||
type Props = {
|
||||
|
@ -29,13 +27,19 @@ type Props = {
|
|||
inviteView?: boolean
|
||||
onInvite?: (id: number) => void
|
||||
selected?: boolean
|
||||
isFollowed?: FollowedInfo
|
||||
}
|
||||
export const AuthorBadge = (props: Props) => {
|
||||
const { mediaMatches } = useMediaQuery()
|
||||
const { author, requireAuthentication } = useSession()
|
||||
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||
const [isSubscribed, setIsSubscribed] = createSignal<boolean>()
|
||||
|
||||
createEffect(() => {
|
||||
if (!subscriptions || !props.author) return
|
||||
const subscribed = subscriptions.authors?.some((authorEntity) => authorEntity.id === props.author?.id)
|
||||
setIsSubscribed(subscribed)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
setIsMobileView(!mediaMatches.sm)
|
||||
|
@ -67,20 +71,11 @@ export const AuthorBadge = (props: Props) => {
|
|||
return props.author.name
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.isFollowed,
|
||||
() => {
|
||||
setIsFollowed(props.isFollowed?.value)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
const handleFollowClick = () => {
|
||||
const value = !isFollowed()
|
||||
requireAuthentication(() => {
|
||||
setIsFollowed(value)
|
||||
setFollowing(FollowingEntity.Author, props.author.slug, value)
|
||||
isSubscribed()
|
||||
? unfollow(FollowingEntity.Author, props.author.slug)
|
||||
: follow(FollowingEntity.Author, props.author.slug)
|
||||
}, 'subscribe')
|
||||
}
|
||||
|
||||
|
@ -134,55 +129,13 @@ export const AuthorBadge = (props: Props) => {
|
|||
</div>
|
||||
<Show when={props.author.slug !== author()?.slug && !props.nameOnly}>
|
||||
<div class={styles.actions}>
|
||||
<Show
|
||||
when={!props.minimizeSubscribeButton}
|
||||
fallback={<CheckButton text={t('Follow')} checked={isFollowed()} onClick={handleFollowClick} />}
|
||||
>
|
||||
<Show
|
||||
when={isFollowed()}
|
||||
fallback={
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
size="S"
|
||||
value={
|
||||
<Show when={props.iconButtons} fallback={t('Subscribe')}>
|
||||
<Icon name="author-subscribe" class={stylesButton.icon} />
|
||||
</Show>
|
||||
}
|
||||
onClick={handleFollowClick}
|
||||
isSubscribeButton={true}
|
||||
class={clsx(styles.actionButton, {
|
||||
[styles.iconed]: props.iconButtons,
|
||||
[stylesButton.subscribed]: isFollowed(),
|
||||
})}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
size="S"
|
||||
value={
|
||||
<Show
|
||||
when={props.iconButtons}
|
||||
fallback={
|
||||
<>
|
||||
<span class={styles.actionButtonLabel}>{t('Following')}</span>
|
||||
<span class={styles.actionButtonLabelHovered}>{t('Unfollow')}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Icon name="author-unsubscribe" class={stylesButton.icon} />
|
||||
</Show>
|
||||
}
|
||||
onClick={handleFollowClick}
|
||||
isSubscribeButton={true}
|
||||
class={clsx(styles.actionButton, {
|
||||
[styles.iconed]: props.iconButtons,
|
||||
[stylesButton.subscribed]: isFollowed(),
|
||||
})}
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
<BadgeSubscribeButton
|
||||
action={() => handleFollowClick()}
|
||||
isSubscribed={isSubscribed()}
|
||||
actionMessageType={
|
||||
subscribeInAction()?.slug === props.author.slug ? subscribeInAction().type : undefined
|
||||
}
|
||||
/>
|
||||
<Show when={props.showMessageButton}>
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
|
|
|
@ -34,16 +34,18 @@ export const AuthorCard = (props: Props) => {
|
|||
const { author, isSessionLoaded, requireAuthentication } = useSession()
|
||||
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
|
||||
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||
const [isSubscribed, setIsSubscribed] = createSignal<boolean>()
|
||||
const isProfileOwner = createMemo(() => author()?.slug === props.author.slug)
|
||||
const { setFollowing, isOwnerSubscribed } = useFollowing()
|
||||
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
|
||||
|
||||
onMount(() => {
|
||||
setAuthorSubs(props.following)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
setIsFollowed(isOwnerSubscribed(props.author?.id))
|
||||
if (!subscriptions || !props.author) return
|
||||
const subscribed = subscriptions.authors?.some((authorEntity) => authorEntity.id === props.author?.id)
|
||||
setIsSubscribed(subscribed)
|
||||
})
|
||||
|
||||
const name = createMemo(() => {
|
||||
|
@ -83,15 +85,19 @@ export const AuthorCard = (props: Props) => {
|
|||
})
|
||||
|
||||
const handleFollowClick = () => {
|
||||
const value = !isFollowed()
|
||||
requireAuthentication(() => {
|
||||
setIsFollowed(value)
|
||||
setFollowing(FollowingEntity.Author, props.author.slug, value)
|
||||
isSubscribed()
|
||||
? unfollow(FollowingEntity.Author, props.author.slug)
|
||||
: follow(FollowingEntity.Author, props.author.slug)
|
||||
}, 'subscribe')
|
||||
}
|
||||
|
||||
const followButtonText = createMemo(() => {
|
||||
if (isOwnerSubscribed(props.author?.id)) {
|
||||
if (subscribeInAction()?.slug === props.author.slug) {
|
||||
return subscribeInAction().type === 'subscribe' ? t('Subscribing...') : t('Unsubscribing...')
|
||||
}
|
||||
|
||||
if (isSubscribed()) {
|
||||
return (
|
||||
<>
|
||||
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
|
||||
|
@ -119,12 +125,7 @@ export const AuthorCard = (props: Props) => {
|
|||
<Show when={props.author.bio}>
|
||||
<div class={styles.authorAbout} innerHTML={props.author.bio} />
|
||||
</Show>
|
||||
<Show
|
||||
when={
|
||||
(props.followers && props.followers.length > 0) ||
|
||||
(props.following && props.following.length > 0)
|
||||
}
|
||||
>
|
||||
<Show when={props.followers?.length > 0 || props.following?.length > 0}>
|
||||
<div class={styles.subscribersContainer}>
|
||||
<Show when={props.followers && props.followers.length > 0}>
|
||||
<a href="?m=followers" class={styles.subscribers}>
|
||||
|
@ -204,13 +205,14 @@ export const AuthorCard = (props: Props) => {
|
|||
when={isProfileOwner()}
|
||||
fallback={
|
||||
<div class={styles.authorActions}>
|
||||
<Show when={authorSubs().length}>
|
||||
<Show when={authorSubs()?.length}>
|
||||
<Button
|
||||
onClick={handleFollowClick}
|
||||
disabled={Boolean(subscribeInAction())}
|
||||
value={followButtonText()}
|
||||
isSubscribeButton={true}
|
||||
class={clsx({
|
||||
[stylesButton.subscribed]: isFollowed(),
|
||||
[stylesButton.subscribed]: isSubscribed(),
|
||||
})}
|
||||
/>
|
||||
</Show>
|
||||
|
@ -255,15 +257,7 @@ export const AuthorCard = (props: Props) => {
|
|||
<div class="row">
|
||||
<div class="col-24">
|
||||
<For each={props.followers}>
|
||||
{(follower: Author) => (
|
||||
<AuthorBadge
|
||||
author={follower}
|
||||
isFollowed={{
|
||||
loaded: Boolean(authorSubs()),
|
||||
value: isOwnerSubscribed(follower.id),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(follower: Author) => <AuthorBadge author={follower} />}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -318,21 +312,9 @@ export const AuthorCard = (props: Props) => {
|
|||
<For each={authorSubs()}>
|
||||
{(subscription) =>
|
||||
isAuthor(subscription) ? (
|
||||
<AuthorBadge
|
||||
isFollowed={{
|
||||
loaded: Boolean(authorSubs()),
|
||||
value: isOwnerSubscribed(subscription.id),
|
||||
}}
|
||||
author={subscription}
|
||||
/>
|
||||
<AuthorBadge author={subscription} />
|
||||
) : (
|
||||
<TopicBadge
|
||||
isFollowed={{
|
||||
loaded: Boolean(authorSubs()),
|
||||
value: isOwnerSubscribed(subscription.id),
|
||||
}}
|
||||
topic={subscription}
|
||||
/>
|
||||
<TopicBadge topic={subscription} />
|
||||
)
|
||||
}
|
||||
</For>
|
||||
|
|
|
@ -21,7 +21,6 @@ const PAGE_SIZE = 20
|
|||
|
||||
export const AuthorsList = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { isOwnerSubscribed } = useFollowing()
|
||||
const { authorsByShouts, authorsByFollowers } = useAuthorsStore()
|
||||
const [loading, setLoading] = createSignal(false)
|
||||
const [currentPage, setCurrentPage] = createSignal({ shouts: 0, followers: 0 })
|
||||
|
@ -83,13 +82,7 @@ export const AuthorsList = (props: Props) => {
|
|||
{(author) => (
|
||||
<div class="row">
|
||||
<div class="col-lg-20 col-xl-18">
|
||||
<AuthorBadge
|
||||
author={author}
|
||||
isFollowed={{
|
||||
loaded: !loading(),
|
||||
value: isOwnerSubscribed(author.id),
|
||||
}}
|
||||
/>
|
||||
<AuthorBadge author={author} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -30,7 +30,6 @@ type Props = {
|
|||
|
||||
export const Beside = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { isOwnerSubscribed } = useFollowing()
|
||||
|
||||
return (
|
||||
<Show when={!!props.beside?.slug && props.values?.length > 0}>
|
||||
|
@ -86,12 +85,7 @@ export const Beside = (props: Props) => {
|
|||
/>
|
||||
</Show>
|
||||
<Show when={props.wrapper === 'author'}>
|
||||
<AuthorBadge
|
||||
author={value as Author}
|
||||
isFollowed={{
|
||||
value: isOwnerSubscribed(value.id),
|
||||
}}
|
||||
/>
|
||||
<AuthorBadge author={value as Author} />
|
||||
</Show>
|
||||
<Show when={props.wrapper === 'article' && value?.slug}>
|
||||
<ArticleCard
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { Show, createMemo, createSignal } from 'solid-js'
|
||||
import { Show, createEffect, createMemo, createSignal } from 'solid-js'
|
||||
|
||||
import { useFollowing } from '../../context/following'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
|
@ -38,14 +38,18 @@ export const TopicCard = (props: TopicProps) => {
|
|||
capitalize(lang() === 'en' ? props.topic.slug.replaceAll('-', ' ') : props.topic.title || ''),
|
||||
)
|
||||
const { author, requireAuthentication } = useSession()
|
||||
const { setFollowing, loading: subLoading } = useFollowing()
|
||||
const [followed, setFollowed] = createSignal()
|
||||
const [isSubscribed, setIsSubscribed] = createSignal()
|
||||
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
|
||||
|
||||
createEffect(() => {
|
||||
if (!subscriptions || !props.topic) return
|
||||
const subscribed = subscriptions.topics?.some((topics) => topics.id === props.topic?.id)
|
||||
setIsSubscribed(subscribed)
|
||||
})
|
||||
|
||||
const handleFollowClick = () => {
|
||||
const value = !followed()
|
||||
requireAuthentication(() => {
|
||||
setFollowed(value)
|
||||
setFollowing(FollowingEntity.Topic, props.topic.slug, value)
|
||||
follow(FollowingEntity.Topic, props.topic.slug)
|
||||
}, 'subscribe')
|
||||
}
|
||||
|
||||
|
@ -53,12 +57,12 @@ export const TopicCard = (props: TopicProps) => {
|
|||
return (
|
||||
<>
|
||||
<Show when={props.iconButton}>
|
||||
<Show when={followed()} fallback="+">
|
||||
<Show when={isSubscribed()} fallback="+">
|
||||
<Icon name="check-subscribed" />
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={!props.iconButton}>
|
||||
<Show when={followed()} fallback={t('Follow')}>
|
||||
<Show when={isSubscribed()} fallback={t('Follow')}>
|
||||
<span class={stylesButton.buttonSubscribeLabelHovered}>{t('Unfollow')}</span>
|
||||
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
|
||||
</Show>
|
||||
|
@ -130,7 +134,7 @@ export const TopicCard = (props: TopicProps) => {
|
|||
fallback={
|
||||
<CheckButton
|
||||
text={t('Follow')}
|
||||
checked={Boolean(followed())}
|
||||
checked={Boolean(isSubscribed())}
|
||||
onClick={handleFollowClick}
|
||||
/>
|
||||
}
|
||||
|
@ -142,10 +146,10 @@ export const TopicCard = (props: TopicProps) => {
|
|||
onClick={handleFollowClick}
|
||||
isSubscribeButton={true}
|
||||
class={clsx(styles.actionButton, {
|
||||
[styles.isSubscribing]: subLoading(),
|
||||
[stylesButton.subscribed]: followed(),
|
||||
[styles.isSubscribing]:
|
||||
subscribeInAction()?.slug === props.topic.slug ? subscribeInAction().type : undefined,
|
||||
[stylesButton.subscribed]: isSubscribed(),
|
||||
})}
|
||||
// disabled={subLoading()}
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
|
|
|
@ -8,16 +8,12 @@ import { useSession } from '../../../context/session'
|
|||
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
|
||||
import { capitalize } from '../../../utils/capitalize'
|
||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||
import { Button } from '../../_shared/Button'
|
||||
import { CheckButton } from '../../_shared/CheckButton'
|
||||
|
||||
import { FollowedInfo } from '../../../pages/types'
|
||||
import { BadgeSubscribeButton } from '../../_shared/BadgeSubscribeButton'
|
||||
import styles from './TopicBadge.module.scss'
|
||||
|
||||
type Props = {
|
||||
topic: Topic
|
||||
minimizeSubscribeButton?: boolean
|
||||
isFollowed?: FollowedInfo
|
||||
showStat?: boolean
|
||||
}
|
||||
|
||||
|
@ -26,14 +22,20 @@ export const TopicBadge = (props: Props) => {
|
|||
const { mediaMatches } = useMediaQuery()
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const { requireAuthentication } = useSession()
|
||||
const { setFollowing, loading: subLoading } = useFollowing()
|
||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||
const [isSubscribed, setIsSubscribed] = createSignal<boolean>()
|
||||
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
|
||||
|
||||
createEffect(() => {
|
||||
if (!subscriptions || !props.topic) return
|
||||
const subscribed = subscriptions.topics?.some((topics) => topics.id === props.topic?.id)
|
||||
setIsSubscribed(subscribed)
|
||||
})
|
||||
|
||||
const handleFollowClick = () => {
|
||||
const value = !isFollowed()
|
||||
requireAuthentication(() => {
|
||||
setIsFollowed(value)
|
||||
setFollowing(FollowingEntity.Topic, props.topic.slug, value)
|
||||
isSubscribed()
|
||||
? follow(FollowingEntity.Topic, props.topic.slug)
|
||||
: unfollow(FollowingEntity.Topic, props.topic.slug)
|
||||
}, 'subscribe')
|
||||
}
|
||||
|
||||
|
@ -41,15 +43,6 @@ export const TopicBadge = (props: Props) => {
|
|||
setIsMobileView(!mediaMatches.sm)
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.isFollowed,
|
||||
() => {
|
||||
setIsFollowed(props.isFollowed.value)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
const title = () =>
|
||||
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
||||
|
||||
|
@ -71,35 +64,14 @@ export const TopicBadge = (props: Props) => {
|
|||
</Show>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class={styles.actions}>
|
||||
<Show
|
||||
when={!props.minimizeSubscribeButton}
|
||||
fallback={
|
||||
<CheckButton text={t('Follow')} checked={Boolean(isFollowed())} onClick={handleFollowClick} />
|
||||
<BadgeSubscribeButton
|
||||
isSubscribed={isSubscribed()}
|
||||
action={handleFollowClick}
|
||||
actionMessageType={
|
||||
subscribeInAction()?.slug === props.topic.slug ? subscribeInAction().type : undefined
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={isFollowed()}
|
||||
fallback={
|
||||
<Button
|
||||
variant="primary"
|
||||
size="S"
|
||||
value={subLoading() ? t('subscribing...') : t('Subscribe')}
|
||||
onClick={handleFollowClick}
|
||||
class={styles.subscribeButton}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
onClick={handleFollowClick}
|
||||
variant="bordered"
|
||||
size="S"
|
||||
value={t('Following')}
|
||||
class={styles.subscribeButton}
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class={styles.stats}>
|
||||
|
|
|
@ -74,8 +74,6 @@ export const AllTopics = (props: Props) => {
|
|||
return keys
|
||||
})
|
||||
|
||||
const { isOwnerSubscribed } = useFollowing()
|
||||
|
||||
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
|
||||
const [searchQuery, setSearchQuery] = createSignal('')
|
||||
const filteredResults = createMemo(() => {
|
||||
|
@ -188,14 +186,7 @@ export const AllTopics = (props: Props) => {
|
|||
<For each={filteredResults().slice(0, limit())}>
|
||||
{(topic) => (
|
||||
<>
|
||||
<TopicBadge
|
||||
topic={topic}
|
||||
isFollowed={{
|
||||
loaded: filteredResults().length > 0,
|
||||
value: isOwnerSubscribed(topic.slug),
|
||||
}}
|
||||
showStat={true}
|
||||
/>
|
||||
<TopicBadge topic={topic} showStat={true} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Author, Reaction, Shout, Topic } from '../../../graphql/schema/cor
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
import { Meta, Title } from '@solidjs/meta'
|
||||
import { clsx } from 'clsx'
|
||||
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, onMount } from 'solid-js'
|
||||
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
|
||||
|
||||
import { useFollowing } from '../../../context/following'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
|
@ -75,7 +75,7 @@ export const AuthorView = (props: Props) => {
|
|||
const bioContainerRef: { current: HTMLDivElement } = { current: null }
|
||||
const bioWrapperRef: { current: HTMLDivElement } = { current: null }
|
||||
|
||||
const fetchData = async (slug) => {
|
||||
const fetchData = async (slug: string) => {
|
||||
try {
|
||||
const [subscriptionsResult, followersResult, authorResult] = await Promise.all([
|
||||
apiClient.getAuthorFollows({ slug }),
|
||||
|
@ -118,7 +118,6 @@ export const AuthorView = (props: Props) => {
|
|||
// pagination
|
||||
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
|
||||
loadMore()
|
||||
loadSubscriptions()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -135,6 +134,7 @@ export const AuthorView = (props: Props) => {
|
|||
|
||||
createEffect(() => {
|
||||
if (author()) {
|
||||
fetchData(author().slug)
|
||||
fetchComments(author())
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||
import { For, Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { apiClient } from '../../../graphql/client/core'
|
||||
import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '../../../graphql/schema/core.gen'
|
||||
import { LayoutType } from '../../../pages/types'
|
||||
import { router } from '../../../stores/router'
|
||||
import { loadShouts, resetSortedArticles, useArticlesStore } from '../../../stores/zine/articles'
|
||||
import { getUnixtime } from '../../../utils/getServerDate'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
|
||||
import { splitToPages } from '../../../utils/splitToPages'
|
||||
import { ArticleCard } from '../../Feed/ArticleCard'
|
||||
import { Button } from '../../_shared/Button'
|
||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||
|
@ -28,19 +26,12 @@ export const PRERENDERED_ARTICLES_COUNT = 36
|
|||
const LOAD_MORE_PAGE_SIZE = 12
|
||||
|
||||
export const Expo = (props: Props) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.shouts))
|
||||
const { t } = useLocalize()
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
|
||||
const [favoriteTopArticles, setFavoriteTopArticles] = createSignal<Shout[]>([])
|
||||
const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal<Shout[]>([])
|
||||
|
||||
const { t } = useLocalize()
|
||||
|
||||
const { sortedArticles } = useArticlesStore({
|
||||
shouts: isLoaded() ? props.shouts : [],
|
||||
layout: props.layout,
|
||||
})
|
||||
|
||||
const [articlesEndPage, setArticlesEndPage] = createSignal<number>(PRERENDERED_ARTICLES_COUNT)
|
||||
const [expoShouts, setExpoShouts] = createSignal<Shout[]>([])
|
||||
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
|
||||
const filters = { ...additionalFilters }
|
||||
|
||||
|
@ -58,15 +49,18 @@ export const Expo = (props: Props) => {
|
|||
const options: LoadShoutsOptions = {
|
||||
filters: getLoadShoutsFilters(),
|
||||
limit: count,
|
||||
offset: sortedArticles().length,
|
||||
offset: expoShouts().length,
|
||||
}
|
||||
|
||||
options.filters = props.layout
|
||||
? { layouts: [props.layout] }
|
||||
: { layouts: ['audio', 'video', 'image', 'literature'] }
|
||||
|
||||
const { hasMore } = await loadShouts(options)
|
||||
const newShouts = await apiClient.getShouts(options)
|
||||
const hasMore = newShouts?.length !== options.limit + 1 && newShouts?.length !== 0
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
|
||||
setExpoShouts((prev) => [...prev, ...newShouts])
|
||||
}
|
||||
|
||||
const loadMoreWithoutScrolling = async (count: number) => {
|
||||
|
@ -100,19 +94,7 @@ export const Expo = (props: Props) => {
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE)
|
||||
setIsLoaded(true)
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
|
||||
loadMore(LOAD_MORE_PAGE_SIZE)
|
||||
}
|
||||
|
||||
loadRandomTopArticles()
|
||||
loadRandomTopMonthArticles()
|
||||
})
|
||||
|
@ -121,9 +103,11 @@ export const Expo = (props: Props) => {
|
|||
on(
|
||||
() => props.layout,
|
||||
() => {
|
||||
resetSortedArticles()
|
||||
setExpoShouts([])
|
||||
setIsLoadMoreButtonVisible(false)
|
||||
setFavoriteTopArticles([])
|
||||
setReactedTopMonthArticles([])
|
||||
setArticlesEndPage(PRERENDERED_ARTICLES_COUNT)
|
||||
loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE)
|
||||
loadRandomTopArticles()
|
||||
loadRandomTopMonthArticles()
|
||||
|
@ -132,16 +116,17 @@ export const Expo = (props: Props) => {
|
|||
)
|
||||
|
||||
onCleanup(() => {
|
||||
resetSortedArticles()
|
||||
setExpoShouts([])
|
||||
})
|
||||
|
||||
const handleLoadMoreClick = () => {
|
||||
loadMoreWithoutScrolling(LOAD_MORE_PAGE_SIZE)
|
||||
setArticlesEndPage((prev) => prev + LOAD_MORE_PAGE_SIZE)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={styles.Expo}>
|
||||
<Show when={sortedArticles()?.length > 0} fallback={<Loading />}>
|
||||
<Show when={expoShouts().length > 0} fallback={<Loading />}>
|
||||
<div class="wide-container">
|
||||
<ul class={clsx('view-switcher')}>
|
||||
<li class={clsx({ 'view-switcher__item--selected': !props.layout })}>
|
||||
|
@ -194,7 +179,7 @@ export const Expo = (props: Props) => {
|
|||
</li>
|
||||
</ul>
|
||||
<div class="row">
|
||||
<For each={sortedArticles().slice(0, LOAD_MORE_PAGE_SIZE)}>
|
||||
<For each={expoShouts()?.slice(0, LOAD_MORE_PAGE_SIZE)}>
|
||||
{(shout) => (
|
||||
<div class="col-md-6 mt-md-5 col-sm-8 mt-sm-3">
|
||||
<ArticleCard
|
||||
|
@ -209,7 +194,7 @@ export const Expo = (props: Props) => {
|
|||
<Show when={reactedTopMonthArticles()?.length > 0} keyed={true}>
|
||||
<ArticleCardSwiper title={t('Top month articles')} slides={reactedTopMonthArticles()} />
|
||||
</Show>
|
||||
<For each={sortedArticles().slice(LOAD_MORE_PAGE_SIZE, LOAD_MORE_PAGE_SIZE * 2)}>
|
||||
<For each={expoShouts().slice(LOAD_MORE_PAGE_SIZE, LOAD_MORE_PAGE_SIZE * 2)}>
|
||||
{(shout) => (
|
||||
<div class="col-md-6 mt-md-5 col-sm-8 mt-sm-3">
|
||||
<ArticleCard
|
||||
|
@ -224,7 +209,7 @@ export const Expo = (props: Props) => {
|
|||
<Show when={favoriteTopArticles()?.length > 0} keyed={true}>
|
||||
<ArticleCardSwiper title={t('Favorite')} slides={favoriteTopArticles()} />
|
||||
</Show>
|
||||
<For each={sortedArticles().slice(LOAD_MORE_PAGE_SIZE * 2)}>
|
||||
<For each={expoShouts().slice(LOAD_MORE_PAGE_SIZE * 2, articlesEndPage())}>
|
||||
{(shout) => (
|
||||
<div class="col-md-6 mt-md-5 col-sm-8 mt-sm-3">
|
||||
<ArticleCard
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
.actionButton {
|
||||
border-radius: 0.8rem !important;
|
||||
margin-right: 0 !important;
|
||||
width: 9em;
|
||||
|
||||
&.iconed {
|
||||
padding: 6px !important;
|
||||
min-width: 4rem;
|
||||
width: unset;
|
||||
|
||||
&:hover img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.actionButtonLabel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.actionButtonLabelHovered {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionButtonLabelHovered {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { Show, createMemo } from 'solid-js'
|
||||
import { useFollowing } from '../../../context/following'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { Button } from '../Button'
|
||||
import stylesButton from '../Button/Button.module.scss'
|
||||
import { CheckButton } from '../CheckButton'
|
||||
import { Icon } from '../Icon'
|
||||
import styles from './BadgeDubscribeButton.module.scss'
|
||||
|
||||
type Props = {
|
||||
class?: string
|
||||
isSubscribed: boolean
|
||||
minimizeSubscribeButton?: boolean
|
||||
action: () => void
|
||||
iconButtons?: boolean
|
||||
actionMessageType?: 'subscribe' | 'unsubscribe'
|
||||
}
|
||||
|
||||
export const BadgeSubscribeButton = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const inActionText = createMemo(() => {
|
||||
return props.actionMessageType === 'subscribe' ? t('Subscribing...') : t('Unsubscribing...')
|
||||
})
|
||||
|
||||
return (
|
||||
<div class={props.class}>
|
||||
<Show
|
||||
when={!props.minimizeSubscribeButton}
|
||||
fallback={<CheckButton text={t('Follow')} checked={props.isSubscribed} onClick={props.action} />}
|
||||
>
|
||||
<Show
|
||||
when={props.isSubscribed}
|
||||
fallback={
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
size="S"
|
||||
value={
|
||||
<Show
|
||||
when={props.iconButtons}
|
||||
fallback={props.actionMessageType ? inActionText() : t('Subscribe')}
|
||||
>
|
||||
<Icon name="author-subscribe" class={stylesButton.icon} />
|
||||
</Show>
|
||||
}
|
||||
onClick={props.action}
|
||||
isSubscribeButton={true}
|
||||
class={clsx(styles.actionButton, {
|
||||
[styles.iconed]: props.iconButtons,
|
||||
[stylesButton.subscribed]: props.isSubscribed,
|
||||
})}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant={props.iconButtons ? 'secondary' : 'bordered'}
|
||||
size="S"
|
||||
value={
|
||||
<Show
|
||||
when={props.iconButtons}
|
||||
fallback={
|
||||
props.actionMessageType ? (
|
||||
inActionText()
|
||||
) : (
|
||||
<>
|
||||
<span class={styles.actionButtonLabel}>{t('Following')}</span>
|
||||
<span class={styles.actionButtonLabelHovered}>{t('Unfollow')}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon name="author-unsubscribe" class={stylesButton.icon} />
|
||||
</Show>
|
||||
}
|
||||
onClick={props.action}
|
||||
isSubscribeButton={true}
|
||||
class={clsx(styles.actionButton, {
|
||||
[styles.iconed]: props.iconButtons,
|
||||
[stylesButton.subscribed]: props.isSubscribed,
|
||||
})}
|
||||
/>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
1
src/components/_shared/BadgeSubscribeButton/index.ts
Normal file
1
src/components/_shared/BadgeSubscribeButton/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { BadgeSubscribeButton } from './BadgeSubscribeButton'
|
|
@ -1,11 +1,19 @@
|
|||
import { Accessor, JSX, createContext, createEffect, createSignal, useContext } from 'solid-js'
|
||||
import { Accessor, JSX, createContext, createEffect, createMemo, createSignal, useContext } from 'solid-js'
|
||||
import { createStore } from 'solid-js/store'
|
||||
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
import { Author, AuthorFollowsResult, FollowingEntity } from '../graphql/schema/core.gen'
|
||||
import { Author, AuthorFollowsResult, Community, FollowingEntity, Topic } from '../graphql/schema/core.gen'
|
||||
|
||||
import { useSession } from './session'
|
||||
|
||||
export type SubscriptionsData = {
|
||||
topics?: Topic[]
|
||||
authors?: Author[]
|
||||
communities?: Community[]
|
||||
}
|
||||
|
||||
type SubscribeAction = { slug: string; type: 'subscribe' | 'unsubscribe' }
|
||||
|
||||
interface FollowingContextType {
|
||||
loading: Accessor<boolean>
|
||||
followers: Accessor<Array<Author>>
|
||||
|
@ -15,7 +23,8 @@ interface FollowingContextType {
|
|||
loadSubscriptions: () => void
|
||||
follow: (what: FollowingEntity, slug: string) => Promise<void>
|
||||
unfollow: (what: FollowingEntity, slug: string) => Promise<void>
|
||||
isOwnerSubscribed: (id: number | string) => boolean
|
||||
// followers: Accessor<Author[]>
|
||||
subscribeInAction?: Accessor<SubscribeAction>
|
||||
}
|
||||
|
||||
const FollowingContext = createContext<FollowingContextType>()
|
||||
|
@ -43,7 +52,6 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
|
|||
console.debug('[context.following] fetching subs data...')
|
||||
const result = await apiClient.getAuthorFollows({ user: session()?.user.id })
|
||||
setSubscriptions(result || EMPTY_SUBSCRIPTIONS)
|
||||
console.info('[context.following] subs:', subscriptions)
|
||||
}
|
||||
} catch (error) {
|
||||
console.info('[context.following] cannot get subs', error)
|
||||
|
@ -52,28 +60,37 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
console.info('[context.following] subs:', subscriptions)
|
||||
})
|
||||
|
||||
const [subscribeInAction, setSubscribeInAction] = createSignal<SubscribeAction>()
|
||||
const follow = async (what: FollowingEntity, slug: string) => {
|
||||
if (!author()) return
|
||||
setSubscribeInAction({ slug, type: 'subscribe' })
|
||||
try {
|
||||
await apiClient.follow({ what, slug })
|
||||
const subscriptionData = await apiClient.follow({ what, slug })
|
||||
setSubscriptions((prevSubscriptions) => {
|
||||
const updatedSubs = { ...prevSubscriptions }
|
||||
if (!updatedSubs[what]) updatedSubs[what] = []
|
||||
const exists = updatedSubs[what]?.some((entity) => entity.slug === slug)
|
||||
if (!exists) updatedSubs[what].push(slug)
|
||||
return updatedSubs
|
||||
if (!prevSubscriptions[what]) prevSubscriptions[what] = []
|
||||
prevSubscriptions[what].push(subscriptionData)
|
||||
return prevSubscriptions
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setSubscribeInAction() // Сбрасываем состояние действия подписки.
|
||||
}
|
||||
}
|
||||
|
||||
const unfollow = async (what: FollowingEntity, slug: string) => {
|
||||
if (!author()) return
|
||||
setSubscribeInAction({ slug: slug, type: 'unsubscribe' })
|
||||
try {
|
||||
await apiClient.unfollow({ what, slug })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setSubscribeInAction()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,23 +131,17 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const isOwnerSubscribed = (id?: number | string) => {
|
||||
if (!author() || !subscriptions) return
|
||||
const isAuthorSubscribed = subscriptions.authors?.some((authorEntity) => authorEntity.id === id)
|
||||
const isTopicSubscribed = subscriptions.topics?.some((topicEntity) => topicEntity.slug === id)
|
||||
return !!isAuthorSubscribed || !!isTopicSubscribed
|
||||
}
|
||||
|
||||
const value: FollowingContextType = {
|
||||
loading,
|
||||
subscriptions,
|
||||
setSubscriptions,
|
||||
isOwnerSubscribed,
|
||||
setFollowing,
|
||||
followers,
|
||||
loadSubscriptions: fetchData,
|
||||
follow,
|
||||
unfollow,
|
||||
// followers,
|
||||
subscribeInAction,
|
||||
}
|
||||
|
||||
return <FollowingContext.Provider value={value}>{props.children}</FollowingContext.Provider>
|
||||
|
|
|
@ -4,6 +4,10 @@ export default gql`
|
|||
mutation FollowMutation($what: FollowingEntity!, $slug: String!) {
|
||||
follow(what: $what, slug: $slug) {
|
||||
error
|
||||
authors {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -53,9 +53,4 @@ export type UploadedFile = {
|
|||
originalFilename?: string
|
||||
}
|
||||
|
||||
export type FollowedInfo = {
|
||||
value?: boolean
|
||||
loaded?: boolean
|
||||
}
|
||||
|
||||
export type SubscriptionFilter = 'all' | 'authors' | 'topics' | 'communities'
|
||||
|
|
Loading…
Reference in New Issue
Block a user