refactoring:following

This commit is contained in:
Untone 2024-05-20 14:16:54 +03:00
parent a25d50d99b
commit 652d0b647a
24 changed files with 290 additions and 236 deletions

View File

@ -10,17 +10,17 @@ 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 { FollowingButton } from '../../_shared/FollowingButton'
import { Icon } from '../../_shared/Icon'
import { Userpic } from '../Userpic'
import styles from './AuthorBadge.module.scss'
type Props = {
author: Author
minimizeSubscribeButton?: boolean
minimize?: boolean
showMessageButton?: boolean
iconButtons?: boolean
nameOnly?: boolean
@ -32,14 +32,14 @@ type Props = {
export const AuthorBadge = (props: Props) => {
const { mediaMatches } = useMediaQuery()
const { author, requireAuthentication } = useSession()
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
const { follow, unfollow, follows, following } = useFollowing()
const [isMobileView, setIsMobileView] = createSignal(false)
const [isSubscribed, setIsSubscribed] = createSignal<boolean>()
const [isFollowed, setIsFollowed] = createSignal<boolean>()
createEffect(() => {
if (!(subscriptions && props.author)) return
const subscribed = subscriptions.authors?.some((authorEntity) => authorEntity.id === props.author?.id)
setIsSubscribed(subscribed)
if (!(follows && props.author)) return
const followed = follows?.authors?.some((authorEntity) => authorEntity.id === props.author?.id)
setIsFollowed(followed)
})
createEffect(() => {
@ -73,9 +73,9 @@ export const AuthorBadge = (props: Props) => {
const handleFollowClick = () => {
requireAuthentication(async () => {
const handle = isSubscribed() ? unfollow : follow
const handle = isFollowed() ? unfollow : follow
await handle(FollowingEntity.Author, props.author.slug)
}, 'subscribe')
}, 'follow')
}
return (
@ -131,12 +131,10 @@ export const AuthorBadge = (props: Props) => {
</div>
<Show when={props.author.slug !== author()?.slug && !props.nameOnly}>
<div class={styles.actions}>
<BadgeSubscribeButton
<FollowingButton
action={() => handleFollowClick()}
isSubscribed={isSubscribed()}
actionMessageType={
subscribeInAction()?.slug === props.author.slug ? subscribeInAction().type : undefined
}
isFollowed={isFollowed()}
actionMessageType={following()?.slug === props.author.slug ? following().type : undefined}
/>
<Show when={props.showMessageButton}>
<Button

View File

@ -8,7 +8,7 @@ import { useFollowing } from '../../../context/following'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
import { SubscriptionFilter } from '../../../pages/types'
import { FollowsFilter } from '../../../pages/types'
import { router, useRouter } from '../../../stores/router'
import { isAuthor } from '../../../utils/isAuthor'
import { translit } from '../../../utils/ru2en'
@ -33,19 +33,19 @@ export const AuthorCard = (props: Props) => {
const { t, lang } = useLocalize()
const { author, isSessionLoaded, requireAuthentication } = useSession()
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
const [isSubscribed, setIsSubscribed] = createSignal<boolean>()
const [followsFilter, setFollowsFilter] = createSignal<FollowsFilter>('all')
const [isFollowed, setIsFollowed] = createSignal<boolean>()
const isProfileOwner = createMemo(() => author()?.slug === props.author.slug)
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
const { follow, unfollow, follows, following } = useFollowing()
onMount(() => {
setAuthorSubs(props.following)
})
createEffect(() => {
if (!(subscriptions && props.author)) return
const subscribed = subscriptions.authors?.some((authorEntity) => authorEntity.id === props.author?.id)
setIsSubscribed(subscribed)
if (!(follows && props.author)) return
const followed = follows?.authors?.some((authorEntity) => authorEntity.id === props.author?.id)
setIsFollowed(followed)
})
const name = createMemo(() => {
@ -72,11 +72,11 @@ export const AuthorCard = (props: Props) => {
createEffect(() => {
if (props.following) {
if (subscriptionFilter() === 'authors') {
if (followsFilter() === 'authors') {
setAuthorSubs(props.following.filter((s) => 'name' in s))
} else if (subscriptionFilter() === 'topics') {
} else if (followsFilter() === 'topics') {
setAuthorSubs(props.following.filter((s) => 'title' in s))
} else if (subscriptionFilter() === 'communities') {
} else if (followsFilter() === 'communities') {
setAuthorSubs(props.following.filter((s) => 'title' in s))
} else {
setAuthorSubs(props.following)
@ -86,18 +86,18 @@ export const AuthorCard = (props: Props) => {
const handleFollowClick = () => {
requireAuthentication(() => {
isSubscribed()
isFollowed()
? unfollow(FollowingEntity.Author, props.author.slug)
: follow(FollowingEntity.Author, props.author.slug)
}, 'subscribe')
}, 'follow')
}
const followButtonText = createMemo(() => {
if (subscribeInAction()?.slug === props.author.slug) {
return subscribeInAction().type === 'subscribe' ? t('Subscribing...') : t('Unsubscribing...')
if (following()?.slug === props.author.slug) {
return following().type === 'follow' ? t('Following...') : t('Unfollowing...')
}
if (isSubscribed()) {
if (isFollowed()) {
return (
<>
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
@ -208,11 +208,11 @@ export const AuthorCard = (props: Props) => {
<Show when={authorSubs()?.length}>
<Button
onClick={handleFollowClick}
disabled={Boolean(subscribeInAction())}
disabled={Boolean(following())}
value={followButtonText()}
isSubscribeButton={true}
class={clsx({
[stylesButton.subscribed]: isSubscribed(),
[stylesButton.followed]: isFollowed(),
})}
/>
</Show>
@ -272,20 +272,20 @@ export const AuthorCard = (props: Props) => {
<ul class="view-switcher">
<li
class={clsx({
'view-switcher__item--selected': subscriptionFilter() === 'all',
'view-switcher__item--selected': followsFilter() === 'all',
})}
>
<button type="button" onClick={() => setSubscriptionFilter('all')}>
<button type="button" onClick={() => setFollowsFilter('all')}>
{t('All')}
</button>
<span class="view-switcher__counter">{props.following.length}</span>
</li>
<li
class={clsx({
'view-switcher__item--selected': subscriptionFilter() === 'authors',
'view-switcher__item--selected': followsFilter() === 'authors',
})}
>
<button type="button" onClick={() => setSubscriptionFilter('authors')}>
<button type="button" onClick={() => setFollowsFilter('authors')}>
{t('Authors')}
</button>
<span class="view-switcher__counter">
@ -294,10 +294,10 @@ export const AuthorCard = (props: Props) => {
</li>
<li
class={clsx({
'view-switcher__item--selected': subscriptionFilter() === 'topics',
'view-switcher__item--selected': followsFilter() === 'topics',
})}
>
<button type="button" onClick={() => setSubscriptionFilter('topics')}>
<button type="button" onClick={() => setFollowsFilter('topics')}>
{t('Topics')}
</button>
<span class="view-switcher__counter">

View File

@ -30,7 +30,7 @@ export default Node.create({
addOptions() {
return {
'data-type': 'incut',
};
}
},
addAttributes() {

View File

@ -17,7 +17,7 @@ export const CustomBlockquote = Blockquote.extend({
content: 'block+',
addOptions(): BlockquoteOptions {
return {} as BlockquoteOptions;
return {} as BlockquoteOptions
},
addAttributes() {
@ -35,15 +35,13 @@ export const CustomBlockquote = Blockquote.extend({
addCommands() {
return {
toggleBlockquote:
(type) => ({ commands }) => commands.toggleWrap(
this.name,
{ 'data-type': type }
),
(type) =>
({ commands }) =>
commands.toggleWrap(this.name, { 'data-type': type }),
setBlockQuoteFloat:
(value) => ({ commands }) => commands.updateAttributes(
this.name,
{ 'data-float': value }
),
(value) =>
({ commands }) =>
commands.updateAttributes(this.name, { 'data-float': value }),
}
},
})

View File

@ -15,7 +15,7 @@ import styles from './Sidebar.module.scss'
export const Sidebar = () => {
const { t } = useLocalize()
const { seen } = useSeen()
const { subscriptions } = useFollowing()
const { follows } = useFollowing()
const { page } = useRouter()
const { articlesByTopic, articlesByAuthor } = useArticlesStore()
const [isSubscriptionsVisible, setSubscriptionsVisible] = createSignal(true)
@ -111,7 +111,7 @@ export const Sidebar = () => {
</li>
</ul>
<Show when={subscriptions.authors.length > 0 || subscriptions.topics.length > 0}>
<Show when={follows?.authors?.length > 0 || follows?.topics?.length > 0}>
<h4
classList={{ [styles.opened]: isSubscriptionsVisible() }}
onClick={() => {
@ -123,7 +123,7 @@ export const Sidebar = () => {
</h4>
<ul class={clsx(styles.subscriptions, { [styles.hidden]: !isSubscriptionsVisible() })}>
<For each={subscriptions.authors}>
<For each={follows.authors}>
{(a: Author) => (
<li>
<a href={`/author/${a.slug}`} classList={{ [styles.unread]: checkAuthorIsSeen(a.slug) }}>
@ -135,7 +135,7 @@ export const Sidebar = () => {
</li>
)}
</For>
<For each={subscriptions.topics}>
<For each={follows.topics}>
{(topic) => (
<li>
<a

View File

@ -123,12 +123,12 @@
width: 9em;
}
.isSubscribing {
.isFollowing {
opacity: 0.5;
}
/*
.isSubscribed {
.isFollowed {
background: #000;
color: #fff;
transition:

View File

@ -1,5 +1,5 @@
import { clsx } from 'clsx'
import { Show, createEffect, createMemo, createSignal } from 'solid-js'
import { Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import { useFollowing } from '../../context/following'
import { useLocalize } from '../../context/localize'
@ -18,7 +18,7 @@ import styles from './Card.module.scss'
interface TopicProps {
topic: Topic
compact?: boolean
subscribed?: boolean
followed?: boolean
shortDescription?: boolean
subscribeButtonBottom?: boolean
additionalClass?: string
@ -27,7 +27,7 @@ interface TopicProps {
showPublications?: boolean
showDescription?: boolean
isCardMode?: boolean
minimizeSubscribeButton?: boolean
minimize?: boolean
isNarrow?: boolean
withIcon?: boolean
}
@ -38,33 +38,40 @@ export const TopicCard = (props: TopicProps) => {
capitalize(lang() === 'en' ? props.topic.slug.replaceAll('-', ' ') : props.topic.title || ''),
)
const { author, requireAuthentication } = useSession()
const [isSubscribed, setIsSubscribed] = createSignal()
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
const [isFollowed, setIsFollowed] = createSignal()
const { follow, unfollow, follows, following } = useFollowing()
createEffect(() => {
if (!(subscriptions && props.topic)) return
const subscribed = subscriptions.topics?.some((topics) => topics.id === props.topic?.id)
setIsSubscribed(subscribed)
})
createEffect(
on(
[() => follows, () => props.topic],
([flws, tpc]) => {
if (flws && tpc) {
const followed = flws.topics?.some((topics) => topics.id === props.topic?.id)
setIsFollowed(followed)
}
},
{ defer: true },
),
)
const handleFollowClick = () => {
requireAuthentication(() => {
isSubscribed()
isFollowed()
? unfollow(FollowingEntity.Topic, props.topic.slug)
: follow(FollowingEntity.Topic, props.topic.slug)
}, 'subscribe')
}, 'follow')
}
const subscribeValue = () => {
return (
<>
<Show when={props.iconButton}>
<Show when={isSubscribed()} fallback="+">
<Icon name="check-subscribed" />
<Show when={isFollowed()} fallback="+">
<Icon name="check-followed" />
</Show>
</Show>
<Show when={!props.iconButton}>
<Show when={isSubscribed()} fallback={t('Follow')}>
<Show when={isFollowed()} fallback={t('Follow')}>
<span class={stylesButton.buttonSubscribeLabelHovered}>{t('Unfollow')}</span>
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
</Show>
@ -132,11 +139,11 @@ export const TopicCard = (props: TopicProps) => {
<ShowOnlyOnClient>
<Show when={author()}>
<Show
when={!props.minimizeSubscribeButton}
when={!props.minimize}
fallback={
<CheckButton
text={t('Follow')}
checked={Boolean(isSubscribed())}
checked={Boolean(isFollowed())}
onClick={handleFollowClick}
/>
}
@ -148,9 +155,9 @@ export const TopicCard = (props: TopicProps) => {
onClick={handleFollowClick}
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.isSubscribing]:
subscribeInAction()?.slug === props.topic.slug ? subscribeInAction().type : undefined,
[stylesButton.subscribed]: isSubscribed(),
[styles.isFollowing]:
following()?.slug === props.topic.slug ? following().type : undefined,
[stylesButton.followed]: isFollowed(),
})}
/>
</Show>

View File

@ -17,14 +17,13 @@ type Props = {
export const FullTopic = (props: Props) => {
const { t } = useLocalize()
const { subscriptions, setFollowing } = useFollowing()
const { follows, changeFollowing } = useFollowing()
const { requireAuthentication } = useSession()
const [followed, setFollowed] = createSignal()
createEffect(() => {
const subs = subscriptions
if (subs?.topics.length !== 0) {
const items = subs.topics || []
if (follows?.topics.length !== 0) {
const items = follows.topics || []
setFollowed(items.some((x: Topic) => x?.slug === props.topic?.slug))
}
})
@ -33,7 +32,7 @@ export const FullTopic = (props: Props) => {
const really = !followed()
setFollowed(really)
requireAuthentication(() => {
setFollowing(FollowingEntity.Topic, props.topic.slug, really)
changeFollowing(FollowingEntity.Topic, props.topic.slug, really)
}, 'follow')
}

View File

@ -45,6 +45,7 @@
.info {
@include font-size(1.4rem);
border: none;
// display: flex;
@ -62,11 +63,13 @@
.title {
@include font-size(2.2rem);
font-weight: bold;
}
.description {
@include font-size(1.6rem);
line-height: 1.4;
margin: 0.8rem 0;
-webkit-line-clamp: 2;
@ -104,6 +107,7 @@
.title {
@include font-size(1.4rem);
font-weight: 500;
line-height: 1em;
color: var(--blue-500);
@ -111,8 +115,9 @@
}
.description {
color: var(--black-400);
@include font-size(1.2rem);
color: var(--black-400);
font-weight: 500;
margin: 0;
}

View File

@ -1,5 +1,5 @@
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 { useLocalize } from '../../../context/localize'
@ -8,12 +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 { BadgeSubscribeButton } from '../../_shared/BadgeSubscribeButton'
import { FollowingButton } from '../../_shared/FollowingButton'
import styles from './TopicBadge.module.scss'
type Props = {
topic: Topic
minimizeSubscribeButton?: boolean
minimize?: boolean
showStat?: boolean
subscriptionsMode?: boolean
}
@ -23,18 +23,25 @@ export const TopicBadge = (props: Props) => {
const { mediaMatches } = useMediaQuery()
const [isMobileView, setIsMobileView] = createSignal(false)
const { requireAuthentication } = useSession()
const [isSubscribed, setIsSubscribed] = createSignal<boolean>()
const { follow, unfollow, subscriptions, subscribeInAction } = useFollowing()
const [isFollowed, setIsFollowed] = createSignal<boolean>()
const { follow, unfollow, follows, following } = useFollowing()
createEffect(() => {
if (!(subscriptions && props.topic)) return
const subscribed = subscriptions.topics?.some((topics) => topics.id === props.topic?.id)
setIsSubscribed(subscribed)
})
createEffect(
on(
[() => follows, () => props.topic],
([flws, tpc]) => {
if (flws && tpc) {
const followed = follows?.topics?.some((topics) => topics.id === props.topic?.id)
setIsFollowed(followed)
}
},
{ defer: true },
),
)
const handleFollowClick = () => {
requireAuthentication(() => {
isSubscribed()
isFollowed()
? follow(FollowingEntity.Topic, props.topic.slug)
: unfollow(FollowingEntity.Topic, props.topic.slug)
}, 'subscribe')
@ -82,12 +89,10 @@ export const TopicBadge = (props: Props) => {
</a>
</div>
<div class={styles.actions}>
<BadgeSubscribeButton
isSubscribed={isSubscribed()}
<FollowingButton
isFollowed={isFollowed()}
action={handleFollowClick}
actionMessageType={
subscribeInAction()?.slug === props.topic.slug ? subscribeInAction().type : undefined
}
actionMessageType={following()?.slug === props.topic.slug ? following().type : undefined}
/>
</div>
</div>

View File

@ -39,54 +39,55 @@ const LOAD_MORE_PAGE_SIZE = 9
export const AuthorView = (props: Props) => {
const { t } = useLocalize()
const { followers: myFollowers } = useFollowing()
const { session } = useSession()
const { followers: myFollowers, follows: myFollows } = useFollowing()
const { session, author: me } = useSession()
const { sortedArticles } = useArticlesStore({ shouts: props.shouts })
const { page: getPage, searchParams } = useRouter()
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const [isBioExpanded, setIsBioExpanded] = createSignal(false)
const [author, setAuthor] = createSignal<Author>()
const [followers, setFollowers] = createSignal([])
const [following, setFollowing] = createSignal<Array<Author | Topic>>([]) // flat AuthorFollowsResult
const [following, changeFollowing] = createSignal<Array<Author | Topic>>([]) // flat AuthorFollowsResult
const [showExpandBioControl, setShowExpandBioControl] = createSignal(false)
const [commented, setCommented] = createSignal<Reaction[]>()
const modal = MODALS[searchParams().m]
const [sessionChecked, setSessionChecked] = createSignal(false)
createEffect(() => {
if (
!sessionChecked() &&
props.authorSlug &&
session()?.user?.app_data?.profile?.slug === props.authorSlug
) {
createEffect(
on(
[() => sessionChecked(), () => props.authorSlug, () => session()?.user?.app_data?.profile?.slug],
([checked, slug, mySlug]) => {
if (!checked && slug && mySlug === slug) {
setSessionChecked(true)
const appdata = session()?.user.app_data
if (appdata) {
console.info('preloaded my own profile')
const { authors, profile, topics } = appdata
setFollowers(myFollowers)
setAuthor(profile)
setFollowing([...authors, ...topics])
setFollowers(myFollowers())
setAuthor(me())
const { authors, topics } = myFollows
changeFollowing([...authors, ...topics])
}
}
})
},
{ defer: true },
),
)
const bioContainerRef: { current: HTMLDivElement } = { current: null }
const bioWrapperRef: { current: HTMLDivElement } = { current: null }
const fetchData = async (slug: string) => {
try {
const [subscriptionsResult, followersResult, authorResult] = await Promise.all([
const [followsResult, followersResult, authorResult] = await Promise.all([
apiClient.getAuthorFollows({ slug }),
apiClient.getAuthorFollowers({ slug }),
loadAuthor({ slug }),
])
const { authors, topics } = subscriptionsResult
setAuthor(authorResult)
setFollowing([...(authors || []), ...(topics || [])])
setFollowers(followersResult || [])
console.info('[components.Author] data loaded')
setAuthor(authorResult)
setFollowers(followersResult || [])
const { authors, topics } = followsResult
changeFollowing([...(authors || []), ...(topics || [])])
} catch (error) {
console.error('[components.Author] fetch error', error)
}

View File

@ -1,12 +1,11 @@
import { clsx } from 'clsx'
import { For, Show, createEffect, createSignal } from 'solid-js'
import { For, Show, createEffect, createSignal, on } from 'solid-js'
import { useFollowing } from '../../../context/following'
import { useLocalize } from '../../../context/localize'
import { Author, Topic } from '../../../graphql/schema/core.gen'
import { SubscriptionFilter } from '../../../pages/types'
import { FollowsFilter } from '../../../pages/types'
import { dummyFilter } from '../../../utils/dummyFilter'
// TODO: refactor styles
import { isAuthor } from '../../../utils/isAuthor'
import { AuthorBadge } from '../../Author/AuthorBadge'
import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation'
@ -19,30 +18,30 @@ import stylesSettings from '../../../styles/FeedSettings.module.scss'
export const ProfileSubscriptions = () => {
const { t, lang } = useLocalize()
const { subscriptions } = useFollowing()
const [following, setFollowing] = createSignal<Array<Author | Topic>>([])
const { follows } = useFollowing()
const [flatFollows, setFlatFollows] = createSignal<Array<Author | Topic>>([])
const [filtered, setFiltered] = createSignal<Array<Author | Topic>>([])
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
const [followsFilter, setFollowsFilter] = createSignal<FollowsFilter>('all')
const [searchQuery, setSearchQuery] = createSignal('')
createEffect(() => {
const { authors, topics } = subscriptions
if (authors || topics) {
const fdata = [...(authors || []), ...(topics || [])]
setFollowing(fdata)
if (subscriptionFilter() === 'authors') {
setFiltered(fdata.filter((s) => 'name' in s))
} else if (subscriptionFilter() === 'topics') {
setFiltered(fdata.filter((s) => 'title' in s))
createEffect(() => setFlatFollows([...(follows?.authors || []), ...(follows?.topics || [])]))
createEffect(
on([flatFollows, followsFilter], ([flat, mode]) => {
if (mode === 'authors') {
setFiltered(flat.filter((s) => 'name' in s))
} else if (mode === 'topics') {
setFiltered(flat.filter((s) => 'title' in s))
} else {
setFiltered(fdata)
setFiltered(flat)
}
}
})
}),
{ defer: true },
)
createEffect(() => {
if (searchQuery()) {
setFiltered(dummyFilter(following(), searchQuery(), lang()))
setFiltered(dummyFilter(flatFollows(), searchQuery(), lang()))
}
})
@ -60,32 +59,32 @@ export const ProfileSubscriptions = () => {
<div class="col-md-20 col-lg-18 col-xl-16">
<h1>{t('My subscriptions')}</h1>
<p class="description">{t('Here you can manage all your Discours subscriptions')}</p>
<Show when={following()} fallback={<Loading />}>
<Show when={flatFollows()} fallback={<Loading />}>
<ul class="view-switcher">
<li
class={clsx({
'view-switcher__item--selected': subscriptionFilter() === 'all',
'view-switcher__item--selected': followsFilter() === 'all',
})}
>
<button type="button" onClick={() => setSubscriptionFilter('all')}>
<button type="button" onClick={() => setFollowsFilter('all')}>
{t('All')}
</button>
</li>
<li
class={clsx({
'view-switcher__item--selected': subscriptionFilter() === 'authors',
'view-switcher__item--selected': followsFilter() === 'authors',
})}
>
<button type="button" onClick={() => setSubscriptionFilter('authors')}>
<button type="button" onClick={() => setFollowsFilter('authors')}>
{t('Authors')}
</button>
</li>
<li
class={clsx({
'view-switcher__item--selected': subscriptionFilter() === 'topics',
'view-switcher__item--selected': followsFilter() === 'topics',
})}
>
<button type="button" onClick={() => setSubscriptionFilter('topics')}>
<button type="button" onClick={() => setFollowsFilter('topics')}>
{t('Topics')}
</button>
</li>
@ -104,9 +103,9 @@ export const ProfileSubscriptions = () => {
{(followingItem) => (
<div>
{isAuthor(followingItem) ? (
<AuthorBadge minimizeSubscribeButton={true} author={followingItem} />
<AuthorBadge minimize={true} author={followingItem} />
) : (
<TopicBadge minimizeSubscribeButton={true} topic={followingItem} />
<TopicBadge minimize={true} topic={followingItem} />
)}
</div>
)}

View File

@ -1 +0,0 @@
export { BadgeSubscribeButton } from './BadgeSubscribeButton'

View File

@ -175,7 +175,7 @@
}
}
&.subscribed {
&.followed {
background: #fff;
color: #000;

View File

@ -2,35 +2,36 @@ import { clsx } from 'clsx'
import { Show, createMemo } from 'solid-js'
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'
import stylesButton from '../Button/Button.module.scss'
import styles from './FollowingButton.module.scss'
type Props = {
class?: string
isSubscribed: boolean
minimizeSubscribeButton?: boolean
isFollowed: boolean
minimize?: boolean
action: () => void
iconButtons?: boolean
actionMessageType?: 'subscribe' | 'unsubscribe'
actionMessageType?: 'follow' | 'unfollow'
}
export const BadgeSubscribeButton = (props: Props) => {
export const FollowingButton = (props: Props) => {
const { t } = useLocalize()
const inActionText = createMemo(() => {
return props.actionMessageType === 'subscribe' ? t('Subscribing...') : t('Unsubscribing...')
return props.actionMessageType === 'follow' ? t('Following...') : t('Unfollowing...')
})
return (
<div class={props.class}>
<Show
when={!props.minimizeSubscribeButton}
fallback={<CheckButton text={t('Follow')} checked={props.isSubscribed} onClick={props.action} />}
when={!props.minimize}
fallback={<CheckButton text={t('Follow')} checked={props.isFollowed} onClick={props.action} />}
>
<Show
when={props.isSubscribed}
when={props.isFollowed}
fallback={
<Button
variant={props.iconButtons ? 'secondary' : 'bordered'}
@ -38,7 +39,7 @@ export const BadgeSubscribeButton = (props: Props) => {
value={
<Show
when={props.iconButtons}
fallback={props.actionMessageType ? inActionText() : t('Subscribe')}
fallback={props.actionMessageType ? inActionText() : t('Follow')}
>
<Icon name="author-subscribe" class={stylesButton.icon} />
</Show>
@ -47,7 +48,7 @@ export const BadgeSubscribeButton = (props: Props) => {
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: props.isSubscribed,
[stylesButton.followed]: props.isFollowed,
})}
/>
}
@ -76,7 +77,7 @@ export const BadgeSubscribeButton = (props: Props) => {
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: props.isSubscribed,
[stylesButton.followed]: props.isFollowed,
})}
/>
</Show>

View File

@ -0,0 +1 @@
export { FollowingButton } from './FollowingButton'

View File

@ -1,30 +1,27 @@
import { Accessor, JSX, createContext, createEffect, createSignal, useContext } from 'solid-js'
import { Accessor, JSX, createContext, createEffect, createSignal, on, useContext } from 'solid-js'
import { createStore } from 'solid-js/store'
import { apiClient } from '../graphql/client/core'
import { Author, AuthorFollowsResult, Community, FollowingEntity, Topic } from '../graphql/schema/core.gen'
import { Author, AuthorFollowsResult, FollowingEntity } 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' }
type FollowingData = { slug: string; type: 'follow' | 'unfollow' }
interface FollowingContextType {
loading: Accessor<boolean>
followers: Accessor<Author[]>
subscriptions: AuthorFollowsResult
setSubscriptions: (subscriptions: AuthorFollowsResult) => void
setFollowing: (what: FollowingEntity, slug: string, value: boolean) => void
loadSubscriptions: () => void
setFollows: (follows: AuthorFollowsResult) => void
following: Accessor<FollowingData>
changeFollowing: (what: FollowingEntity, slug: string, value: boolean) => void
follows: AuthorFollowsResult
loadFollows: () => void
follow: (what: FollowingEntity, slug: string) => Promise<void>
unfollow: (what: FollowingEntity, slug: string) => Promise<void>
// followers: Accessor<Author[]>
subscribeInAction?: Accessor<SubscribeAction>
}
const FollowingContext = createContext<FollowingContextType>()
@ -42,7 +39,7 @@ const EMPTY_SUBSCRIPTIONS: AuthorFollowsResult = {
export const FollowingProvider = (props: { children: JSX.Element }) => {
const [loading, setLoading] = createSignal<boolean>(false)
const [followers, setFollowers] = createSignal<Author[]>([])
const [subscriptions, setSubscriptions] = createStore<AuthorFollowsResult>(EMPTY_SUBSCRIPTIONS)
const [follows, setFollows] = createStore<AuthorFollowsResult>(EMPTY_SUBSCRIPTIONS)
const { author, session } = useSession()
const fetchData = async () => {
@ -51,7 +48,7 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
if (apiClient.private) {
console.debug('[context.following] fetching subs data...')
const result = await apiClient.getAuthorFollows({ user: session()?.user.id })
setSubscriptions(result || EMPTY_SUBSCRIPTIONS)
setFollows(result || EMPTY_SUBSCRIPTIONS)
}
} catch (error) {
console.info('[context.following] cannot get subs', error)
@ -60,47 +57,51 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
}
}
createEffect(() => {
console.info('[context.following] subs:', subscriptions)
})
const [subscribeInAction, setSubscribeInAction] = createSignal<SubscribeAction>()
const [following, setFollowing] = createSignal<FollowingData>()
const follow = async (what: FollowingEntity, slug: string) => {
if (!author()) return
setSubscribeInAction({ slug, type: 'subscribe' })
setFollowing({ slug, type: 'follow' })
try {
const subscriptionData = await apiClient.follow({ what, slug })
setSubscriptions((prevSubscriptions) => {
if (!prevSubscriptions[what]) prevSubscriptions[what] = []
prevSubscriptions[what].push(subscriptionData)
return prevSubscriptions
const result = await apiClient.follow({ what, slug })
setFollows((subs) => {
if (result.authors) subs['authors'] = result.authors || []
if (result.topics) subs['topics'] = result.topics || []
return subs
})
} catch (error) {
console.error(error)
} finally {
setSubscribeInAction() // Сбрасываем состояние действия подписки.
setFollowing() // Сбрасываем состояние действия подписки.
}
}
const unfollow = async (what: FollowingEntity, slug: string) => {
if (!author()) return
setSubscribeInAction({ slug: slug, type: 'unsubscribe' })
setFollowing({ slug: slug, type: 'unfollow' })
try {
await apiClient.unfollow({ what, slug })
const result = await apiClient.unfollow({ what, slug })
setFollows((subs) => {
if (result.authors) subs['authors'] = result.authors || []
if (result.topics) subs['topics'] = result.topics || []
return subs
})
} catch (error) {
console.error(error)
} finally {
setSubscribeInAction()
setFollowing()
}
}
createEffect(() => {
if (author()) {
createEffect(
on(
() => author(),
(a) => {
if (a?.id) {
try {
const appdata = session()?.user.app_data
if (appdata) {
const { authors, followers, topics } = appdata
setSubscriptions({ authors, topics })
setFollows({ authors, topics })
setFollowers(followers)
if (!authors) fetchData()
}
@ -108,11 +109,13 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
console.error(e)
}
}
})
},
),
)
const setFollowing = (what: FollowingEntity, slug: string, value = true) => {
setSubscriptions((prevSubscriptions) => {
const updatedSubs = { ...prevSubscriptions }
const changeFollowing = (what: FollowingEntity, slug: string, value = true) => {
setFollows((fff) => {
const updatedSubs = { ...fff }
if (!updatedSubs[what]) updatedSubs[what] = []
if (value) {
const exists = updatedSubs[what]?.some((entity) => entity.slug === slug)
@ -133,15 +136,14 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
const value: FollowingContextType = {
loading,
subscriptions,
setSubscriptions,
setFollowing,
follows,
setFollows,
following,
changeFollowing,
followers,
loadSubscriptions: fetchData,
loadFollows: fetchData,
follow,
unfollow,
// followers,
subscribeInAction,
}
return <FollowingContext.Provider value={value}>{props.children}</FollowingContext.Provider>

View File

@ -6,7 +6,24 @@ export default gql`
error
authors {
id
name
slug
pic
bio
stat {
followers
shouts
comments
}
}
topics {
body
slug
stat {
shouts
authors
followers
}
}
}
}

View File

@ -3,6 +3,27 @@ export default gql`
mutation UnfollowMutation($what: FollowingEntity!, $slug: String!) {
unfollow(what: $what, slug: $slug) {
error
authors {
id
name
slug
pic
bio
stat {
followers
shouts
comments
}
}
topics {
body
slug
stat {
shouts
authors
followers
}
}
}
}
`

View File

@ -53,4 +53,4 @@ export type UploadedFile = {
originalFilename?: string
}
export type SubscriptionFilter = 'all' | 'authors' | 'topics' | 'communities'
export type FollowsFilter = 'all' | 'authors' | 'topics' | 'communities'

View File

@ -14,8 +14,9 @@ const cssModuleHMR = () => {
const { modules } = context
modules.forEach((module) => {
if (module.id.includes('.module.scss')) {
if (module.id.includes('.scss') || module.id.includes('.css')) {
module.isSelfAccepting = true
module.accept()
}
})
},