refactoring:following
This commit is contained in:
parent
a25d50d99b
commit
652d0b647a
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -30,7 +30,7 @@ export default Node.create({
|
|||
addOptions() {
|
||||
return {
|
||||
'data-type': 'incut',
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
|
|
|
@ -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 }),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -123,12 +123,12 @@
|
|||
width: 9em;
|
||||
}
|
||||
|
||||
.isSubscribing {
|
||||
.isFollowing {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*
|
||||
.isSubscribed {
|
||||
.isFollowed {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
transition:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export { BadgeSubscribeButton } from './BadgeSubscribeButton'
|
|
@ -175,7 +175,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.subscribed {
|
||||
&.followed {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
|
||||
|
|
|
@ -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>
|
1
src/components/_shared/FollowingButton/index.ts
Normal file
1
src/components/_shared/FollowingButton/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { FollowingButton } from './FollowingButton'
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -53,4 +53,4 @@ export type UploadedFile = {
|
|||
originalFilename?: string
|
||||
}
|
||||
|
||||
export type SubscriptionFilter = 'all' | 'authors' | 'topics' | 'communities'
|
||||
export type FollowsFilter = 'all' | 'authors' | 'topics' | 'communities'
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue
Block a user