subs-refactoring
This commit is contained in:
parent
bec333f7c3
commit
79f94876f0
|
@ -423,14 +423,14 @@
|
|||
"subscriber": "subscriber",
|
||||
"subscriber_rp": "subscriber",
|
||||
"subscribers": "subscribers",
|
||||
"SubscriberWithCount": "{count, plural, =0 {no followers} one {{count} follower} other {{count} followers}}",
|
||||
"FollowersWithCount": "{count, plural, =0 {no followers} one {{count} follower} other {{count} followers}}",
|
||||
"subscribing...": "subscribing...",
|
||||
"subscription": "subscription",
|
||||
"Subscription": "Subscription",
|
||||
"subscription_rp": "subscription",
|
||||
"subscriptions": "subscriptions",
|
||||
"Subscriptions": "Subscriptions",
|
||||
"SubscriptionWithCount": "{count, plural, =0 {no subscriptions} one {{count} subscription} other {{count} subscriptions}}",
|
||||
"FollowsWithCount": "{count, plural, =0 {no subscriptions} one {{count} subscription} other {{count} subscriptions}}",
|
||||
"Substrate": "Substrate",
|
||||
"Success": "Success",
|
||||
"Successfully authorized": "Authorization successful",
|
||||
|
@ -541,4 +541,4 @@
|
|||
"You've reached a non-existed page": "You've reached a non-existed page",
|
||||
"Your email": "Your email",
|
||||
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses"
|
||||
}
|
||||
}
|
|
@ -451,11 +451,11 @@
|
|||
"subscriber": "подписчик",
|
||||
"subscriber_rp": "подписчика",
|
||||
"subscribers": "подписчиков",
|
||||
"SubscriberWithCount": "{count, plural, =0 {нет подписчиков} one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}}",
|
||||
"FollowersWithCount": "{count, plural, =0 {нет подписчиков} one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}}",
|
||||
"subscribing...": "Подписка...",
|
||||
"Subscription": "Подписка",
|
||||
"Subscriptions": "Подписки",
|
||||
"SubscriptionWithCount": "{count, plural, =0 {нет подписок} one {{count} подписка} few {{count} подписки} other {{count} подписок}}",
|
||||
"FollowsWithCount": "{count, plural, =0 {нет подписок} one {{count} подписка} few {{count} подписки} other {{count} подписок}}",
|
||||
"Substrate": "Подложка",
|
||||
"Success": "Успешно",
|
||||
"Successfully authorized": "Авторизация успешна",
|
||||
|
@ -568,4 +568,4 @@
|
|||
"You've successfully logged out": "Вы успешно вышли из аккаунта",
|
||||
"Your email": "Ваш email",
|
||||
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах"
|
||||
}
|
||||
}
|
|
@ -31,16 +31,7 @@ export const AudioPlayer = (props: Props) => {
|
|||
const [isPlaying, setIsPlaying] = createSignal(false)
|
||||
|
||||
const currentTack = createMemo(() => props.media[currentTrackIndex()])
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => currentTrackIndex(),
|
||||
() => {
|
||||
setCurrentTrackDuration(0)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
createEffect(on(currentTrackIndex, () => setCurrentTrackDuration(0), { defer: true }))
|
||||
|
||||
const handlePlayMedia = async (trackIndex: number) => {
|
||||
setIsPlaying(!isPlaying() || trackIndex !== currentTrackIndex())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { openPage } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { Match, Show, Switch, createEffect, createMemo, createSignal } from 'solid-js'
|
||||
import { Match, Show, Switch, createEffect, createMemo, createSignal, on } from 'solid-js'
|
||||
|
||||
import { useFollowing } from '../../../context/following'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
|
@ -34,17 +34,19 @@ export const AuthorBadge = (props: Props) => {
|
|||
const { author, requireAuthentication } = useSession()
|
||||
const { follow, unfollow, follows, following } = useFollowing()
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||
|
||||
createEffect(() => {
|
||||
if (!(follows && props.author)) return
|
||||
const followed = follows?.authors?.some((authorEntity) => authorEntity.id === props.author?.id)
|
||||
setIsFollowed(followed)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
setIsMobileView(!mediaMatches.sm)
|
||||
})
|
||||
const [isFollowed, setIsFollowed] = createSignal<boolean>(
|
||||
follows?.authors?.some((authorEntity) => authorEntity.id === props.author?.id),
|
||||
)
|
||||
createEffect(() => setIsMobileView(!mediaMatches.sm))
|
||||
createEffect(
|
||||
on(
|
||||
[() => follows?.authors, () => props.author, following],
|
||||
([followingAuthors, currentAuthor, _]) => {
|
||||
setIsFollowed(followingAuthors?.some((followedAuthor) => followedAuthor.id === currentAuthor?.id))
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
const { changeSearchParams } = useRouter()
|
||||
const { t, formatDate, lang } = useLocalize()
|
||||
|
@ -132,7 +134,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
<Show when={props.author.slug !== author()?.slug && !props.nameOnly}>
|
||||
<div class={styles.actions}>
|
||||
<FollowingButton
|
||||
action={() => handleFollowClick()}
|
||||
action={handleFollowClick}
|
||||
isFollowed={isFollowed()}
|
||||
actionMessageType={following()?.slug === props.author.slug ? following().type : undefined}
|
||||
/>
|
||||
|
|
|
@ -429,7 +429,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.subscribersContainer {
|
||||
.followersContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 1.4rem;
|
||||
|
@ -440,7 +440,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.subscribers {
|
||||
.followers {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
|
@ -456,7 +456,7 @@
|
|||
margin-right: 0;
|
||||
}
|
||||
|
||||
.subscribersItem {
|
||||
.followersItem {
|
||||
position: relative;
|
||||
|
||||
&:nth-child(1) {
|
||||
|
@ -473,7 +473,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.subscribersCounter {
|
||||
.followsCounter {
|
||||
font-weight: 500;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
@ -481,7 +481,7 @@
|
|||
&:hover {
|
||||
background: none !important;
|
||||
|
||||
.subscribersCounter {
|
||||
.followsCounter {
|
||||
background: var(--background-color-invert);
|
||||
}
|
||||
}
|
||||
|
@ -489,4 +489,4 @@
|
|||
|
||||
.listWrapper {
|
||||
max-height: 70vh;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ import styles from './AuthorCard.module.scss'
|
|||
type Props = {
|
||||
author: Author
|
||||
followers?: Author[]
|
||||
following?: Array<Author | Topic>
|
||||
flatFollows?: Array<Author | Topic>
|
||||
}
|
||||
export const AuthorCard = (props: Props) => {
|
||||
const { t, lang } = useLocalize()
|
||||
|
@ -39,7 +39,7 @@ export const AuthorCard = (props: Props) => {
|
|||
const { follow, unfollow, follows, following } = useFollowing()
|
||||
|
||||
onMount(() => {
|
||||
setAuthorSubs(props.following)
|
||||
setAuthorSubs(props.flatFollows)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
|
@ -71,15 +71,15 @@ export const AuthorCard = (props: Props) => {
|
|||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (props.following) {
|
||||
if (props.flatFollows) {
|
||||
if (followsFilter() === 'authors') {
|
||||
setAuthorSubs(props.following.filter((s) => 'name' in s))
|
||||
setAuthorSubs(props.flatFollows.filter((s) => 'name' in s))
|
||||
} else if (followsFilter() === 'topics') {
|
||||
setAuthorSubs(props.following.filter((s) => 'title' in s))
|
||||
setAuthorSubs(props.flatFollows.filter((s) => 'title' in s))
|
||||
} else if (followsFilter() === 'communities') {
|
||||
setAuthorSubs(props.following.filter((s) => 'title' in s))
|
||||
setAuthorSubs(props.flatFollows.filter((s) => 'title' in s))
|
||||
} else {
|
||||
setAuthorSubs(props.following)
|
||||
setAuthorSubs(props.flatFollows)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -108,6 +108,73 @@ export const AuthorCard = (props: Props) => {
|
|||
return t('Follow')
|
||||
})
|
||||
|
||||
const FollowersModalView = () => (
|
||||
<>
|
||||
<h2>{t('Followers')}</h2>
|
||||
<div class={styles.listWrapper}>
|
||||
<div class="row">
|
||||
<div class="col-24">
|
||||
<For each={props.followers}>{(follower: Author) => <AuthorBadge author={follower} />}</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
const FollowingModalView = () => (
|
||||
<>
|
||||
<h2>{t('Subscriptions')}</h2>
|
||||
<ul class="view-switcher">
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': followsFilter() === 'all',
|
||||
})}
|
||||
>
|
||||
<button type="button" onClick={() => setFollowsFilter('all')}>
|
||||
{t('All')}
|
||||
</button>
|
||||
<span class="view-switcher__counter">{props.flatFollows.length}</span>
|
||||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': followsFilter() === 'authors',
|
||||
})}
|
||||
>
|
||||
<button type="button" onClick={() => setFollowsFilter('authors')}>
|
||||
{t('Authors')}
|
||||
</button>
|
||||
<span class="view-switcher__counter">{props.flatFollows.filter((s) => 'name' in s).length}</span>
|
||||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': followsFilter() === 'topics',
|
||||
})}
|
||||
>
|
||||
<button type="button" onClick={() => setFollowsFilter('topics')}>
|
||||
{t('Topics')}
|
||||
</button>
|
||||
<span class="view-switcher__counter">{props.flatFollows.filter((s) => 'title' in s).length}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<div class={styles.listWrapper}>
|
||||
<div class="row">
|
||||
<div class="col-24">
|
||||
<For each={authorSubs()}>
|
||||
{(subscription) =>
|
||||
isAuthor(subscription) ? (
|
||||
<AuthorBadge author={subscription} subscriptionsMode={true} />
|
||||
) : (
|
||||
<TopicBadge topic={subscription} subscriptionsMode={true} />
|
||||
)
|
||||
}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.author, 'row')}>
|
||||
<div class="col-md-5">
|
||||
|
@ -125,35 +192,30 @@ export const AuthorCard = (props: Props) => {
|
|||
<Show when={props.author.bio}>
|
||||
<div class={styles.authorAbout} innerHTML={props.author.bio} />
|
||||
</Show>
|
||||
<Show when={props.followers?.length > 0 || props.following?.length > 0}>
|
||||
<div class={styles.subscribersContainer}>
|
||||
<Show when={props.followers?.length > 0 || props.flatFollows?.length > 0}>
|
||||
<div class={styles.followersContainer}>
|
||||
<Show when={props.followers && props.followers.length > 0}>
|
||||
<a href="?m=followers" class={styles.subscribers}>
|
||||
<a href="?m=followers" class={styles.followers}>
|
||||
<For each={props.followers.slice(0, 3)}>
|
||||
{(f) => (
|
||||
<Userpic size={'XS'} name={f.name} userpic={f.pic} class={styles.subscribersItem} />
|
||||
<Userpic size={'XS'} name={f.name} userpic={f.pic} class={styles.followersItem} />
|
||||
)}
|
||||
</For>
|
||||
<div class={styles.subscribersCounter}>
|
||||
{t('SubscriberWithCount', {
|
||||
<div class={styles.followsCounter}>
|
||||
{t('FollowersWithCount', {
|
||||
count: props.followers.length ?? 0,
|
||||
})}
|
||||
</div>
|
||||
</a>
|
||||
</Show>
|
||||
|
||||
<Show when={props.following && props.following.length > 0}>
|
||||
<a href="?m=following" class={styles.subscribers}>
|
||||
<For each={props.following.slice(0, 3)}>
|
||||
<Show when={props.flatFollows?.length > 0}>
|
||||
<a href="?m=following" class={styles.followers}>
|
||||
<For each={props.flatFollows.slice(0, 3)}>
|
||||
{(f) => {
|
||||
if ('name' in f) {
|
||||
return (
|
||||
<Userpic
|
||||
size={'XS'}
|
||||
name={f.name}
|
||||
userpic={f.pic}
|
||||
class={styles.subscribersItem}
|
||||
/>
|
||||
<Userpic size={'XS'} name={f.name} userpic={f.pic} class={styles.followersItem} />
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -163,7 +225,7 @@ export const AuthorCard = (props: Props) => {
|
|||
size={'XS'}
|
||||
name={f.title}
|
||||
userpic={f.pic}
|
||||
class={styles.subscribersItem}
|
||||
class={styles.followersItem}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -171,9 +233,9 @@ export const AuthorCard = (props: Props) => {
|
|||
return null
|
||||
}}
|
||||
</For>
|
||||
<div class={styles.subscribersCounter}>
|
||||
{t('SubscriptionWithCount', {
|
||||
count: props?.following.length ?? 0,
|
||||
<div class={styles.followsCounter}>
|
||||
{t('FollowsWithCount', {
|
||||
count: props?.flatFollows.length ?? 0,
|
||||
})}
|
||||
</div>
|
||||
</a>
|
||||
|
@ -251,77 +313,12 @@ export const AuthorCard = (props: Props) => {
|
|||
</ShowOnlyOnClient>
|
||||
<Show when={props.followers}>
|
||||
<Modal variant="medium" isResponsive={true} name="followers" maxHeight>
|
||||
<>
|
||||
<h2>{t('Followers')}</h2>
|
||||
<div class={styles.listWrapper}>
|
||||
<div class="row">
|
||||
<div class="col-24">
|
||||
<For each={props.followers}>
|
||||
{(follower: Author) => <AuthorBadge author={follower} />}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<FollowersModalView />
|
||||
</Modal>
|
||||
</Show>
|
||||
<Show when={props.following}>
|
||||
<Show when={props.flatFollows}>
|
||||
<Modal variant="medium" isResponsive={true} name="following" maxHeight>
|
||||
<>
|
||||
<h2>{t('Subscriptions')}</h2>
|
||||
<ul class="view-switcher">
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': followsFilter() === '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': followsFilter() === 'authors',
|
||||
})}
|
||||
>
|
||||
<button type="button" onClick={() => setFollowsFilter('authors')}>
|
||||
{t('Authors')}
|
||||
</button>
|
||||
<span class="view-switcher__counter">
|
||||
{props.following.filter((s) => 'name' in s).length}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': followsFilter() === 'topics',
|
||||
})}
|
||||
>
|
||||
<button type="button" onClick={() => setFollowsFilter('topics')}>
|
||||
{t('Topics')}
|
||||
</button>
|
||||
<span class="view-switcher__counter">
|
||||
{props.following.filter((s) => 'title' in s).length}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<div class={styles.listWrapper}>
|
||||
<div class="row">
|
||||
<div class="col-24">
|
||||
<For each={authorSubs()}>
|
||||
{(subscription) =>
|
||||
isAuthor(subscription) ? (
|
||||
<AuthorBadge author={subscription} subscriptionsMode={true} />
|
||||
) : (
|
||||
<TopicBadge topic={subscription} subscriptionsMode={true} />
|
||||
)
|
||||
}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<FollowingModalView />
|
||||
</Modal>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
@ -63,18 +63,8 @@ export const PasswordField = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => error(),
|
||||
() => {
|
||||
props.errorMessage?.(error())
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
createEffect(() => {
|
||||
setError(props.setError)
|
||||
})
|
||||
createEffect(on(error, (er) => er && props.errorMessage?.(er), { defer: true }))
|
||||
createEffect(() => setError(props.setError))
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.PassportField, props.class)}>
|
||||
|
|
|
@ -62,7 +62,7 @@ export const TableOfContents = (props: Props) => {
|
|||
createEffect(
|
||||
on(
|
||||
() => props.body,
|
||||
() => debouncedUpdateHeadings(),
|
||||
(_) => debouncedUpdateHeadings(),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -40,65 +40,19 @@ const LOAD_MORE_PAGE_SIZE = 9
|
|||
export const AuthorView = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { followers: myFollowers, follows: myFollows } = useFollowing()
|
||||
const { session, author: me } = useSession()
|
||||
const { 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 [author, setAuthor] = createSignal<Author>(props.author)
|
||||
const [followers, setFollowers] = createSignal([])
|
||||
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(
|
||||
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')
|
||||
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 [followsResult, followersResult, authorResult] = await Promise.all([
|
||||
apiClient.getAuthorFollows({ slug }),
|
||||
apiClient.getAuthorFollowers({ slug }),
|
||||
loadAuthor({ slug }),
|
||||
])
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
const checkBioHeight = () => {
|
||||
if (bioContainerRef.current) {
|
||||
setShowExpandBioControl(bioContainerRef.current.offsetHeight > bioWrapperRef.current.offsetHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// пагинация загрузки ленты постов
|
||||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
const { hasMore } = await loadShouts({
|
||||
|
@ -110,36 +64,72 @@ export const AuthorView = (props: Props) => {
|
|||
restoreScrollPosition()
|
||||
}
|
||||
|
||||
// загружает профиль и подписки
|
||||
const [isFetching, setIsFetching] = createSignal(false)
|
||||
const fetchData = async (slug) => {
|
||||
setIsFetching(true)
|
||||
const authorResult = await loadAuthor({ slug })
|
||||
setAuthor(authorResult)
|
||||
console.info(`[Author] profile for @${slug} fetched`)
|
||||
|
||||
const followsResult = await apiClient.getAuthorFollows({ slug })
|
||||
const { authors, topics } = followsResult
|
||||
changeFollowing([...(authors || []), ...(topics || [])])
|
||||
console.info(`[Author] follows for @${slug} fetched`)
|
||||
|
||||
const followersResult = await apiClient.getAuthorFollowers({ slug })
|
||||
setFollowers(followersResult || [])
|
||||
console.info(`[Author] followers for @${slug} fetched`)
|
||||
setIsFetching(false)
|
||||
}
|
||||
|
||||
// проверяет не собственный ли это профиль, иначе - загружает
|
||||
createEffect(
|
||||
on([() => me(), () => props.authorSlug], ([myProfile, slug]) => {
|
||||
const my = slug && myProfile?.slug === slug
|
||||
if (my) {
|
||||
console.debug('[Author] my profile precached')
|
||||
myProfile && setAuthor(myProfile)
|
||||
setFollowers(myFollowers() || [])
|
||||
changeFollowing([...(myFollows?.authors || []), ...(myFollows?.topics || [])])
|
||||
} else if (slug && !isFetching()) {
|
||||
fetchData(slug)
|
||||
}
|
||||
}),
|
||||
{ defer: true },
|
||||
)
|
||||
|
||||
// догружает ленту и комментарии
|
||||
createEffect(
|
||||
on(author, async (profile) => {
|
||||
if (!commented() && profile) {
|
||||
await loadMore()
|
||||
|
||||
const ccc = await apiClient.getReactionsBy({
|
||||
by: { comment: true, created_by: profile.id },
|
||||
})
|
||||
setCommented(ccc)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const bioContainerRef: { current: HTMLDivElement } = { current: null }
|
||||
const bioWrapperRef: { current: HTMLDivElement } = { current: null }
|
||||
const checkBioHeight = () => {
|
||||
if (bioContainerRef.current) {
|
||||
setShowExpandBioControl(bioContainerRef.current.offsetHeight > bioWrapperRef.current.offsetHeight)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!modal) hideModal()
|
||||
fetchData(props.authorSlug)
|
||||
checkBioHeight()
|
||||
loadMore()
|
||||
})
|
||||
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE),
|
||||
)
|
||||
|
||||
const fetchComments = async (commenter: Author) => {
|
||||
const data = await apiClient.getReactionsBy({
|
||||
by: { comment: true, created_by: commenter.id },
|
||||
})
|
||||
setCommented(data)
|
||||
}
|
||||
|
||||
const authorSlug = createMemo(() => author()?.slug)
|
||||
createEffect(
|
||||
on(
|
||||
() => authorSlug(),
|
||||
() => {
|
||||
fetchData(authorSlug())
|
||||
fetchComments(author())
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
const ogImage = createMemo(() =>
|
||||
author()?.pic
|
||||
? getImageUrl(author()?.pic, { width: 1200 })
|
||||
|
@ -168,7 +158,7 @@ export const AuthorView = (props: Props) => {
|
|||
<Show when={author()} fallback={<Loading />}>
|
||||
<>
|
||||
<div class={styles.authorHeader}>
|
||||
<AuthorCard author={author()} followers={followers() || []} following={following() || []} />
|
||||
<AuthorCard author={author()} followers={followers() || []} flatFollows={following() || []} />
|
||||
</div>
|
||||
<div class={clsx(styles.groupControls, 'row')}>
|
||||
<div class="col-md-16">
|
||||
|
|
|
@ -143,16 +143,20 @@ export const FeedView = (props: Props) => {
|
|||
Promise.all([loadTopComments()]).finally(() => setIsRightColumnLoaded(true))
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (session()?.access_token && !unratedArticles()) {
|
||||
loadUnratedArticles()
|
||||
}
|
||||
})
|
||||
createEffect(
|
||||
on(
|
||||
[() => session(), unratedArticles],
|
||||
([s, seen]) => {
|
||||
if (s?.access_token && !(seen?.length > 0)) loadUnratedArticles()
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => page().route + searchParams().by + searchParams().period + searchParams().visibility,
|
||||
() => {
|
||||
[page, searchParams],
|
||||
(_, _p) => {
|
||||
resetSortedArticles()
|
||||
loadMore()
|
||||
},
|
||||
|
|
|
@ -136,21 +136,18 @@ export const InboxView = (props: Props) => {
|
|||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => messages(),
|
||||
() => {
|
||||
if (!messagesContainerRef.current) {
|
||||
return
|
||||
}
|
||||
if (messagesContainerRef.current.scrollTop >= messagesContainerRef.current.scrollHeight) {
|
||||
return
|
||||
}
|
||||
messagesContainerRef.current.scroll({
|
||||
top: messagesContainerRef.current.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
},
|
||||
),
|
||||
on(messages, () => {
|
||||
if (!messagesContainerRef.current) {
|
||||
return
|
||||
}
|
||||
if (messagesContainerRef.current.scrollTop >= messagesContainerRef.current.scrollHeight) {
|
||||
return
|
||||
}
|
||||
messagesContainerRef.current.scroll({
|
||||
top: messagesContainerRef.current.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}),
|
||||
{ defer: true },
|
||||
)
|
||||
const handleScrollMessageContainer = () => {
|
||||
|
|
|
@ -87,13 +87,7 @@ export const TopicView = (props: Props) => {
|
|||
loadReactedTopMonthArticles(topic()?.slug)
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => topic(),
|
||||
() => loadRandom(),
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
createEffect(on(topic, loadRandom, { defer: true }))
|
||||
|
||||
const title = createMemo(
|
||||
() =>
|
||||
|
|
|
@ -66,7 +66,7 @@ export const InviteMembers = (props: Props) => {
|
|||
|
||||
createEffect(
|
||||
on(
|
||||
() => sortedAuthors(),
|
||||
sortedAuthors,
|
||||
(currentAuthors) => {
|
||||
setAuthorsToInvite(currentAuthors.map((author) => ({ ...author, selected: false })))
|
||||
},
|
||||
|
|
|
@ -129,8 +129,8 @@ export const Lightbox = (props: Props) => {
|
|||
|
||||
createEffect(
|
||||
on(
|
||||
() => zoomLevel(),
|
||||
() => {
|
||||
zoomLevel,
|
||||
(_) => {
|
||||
clearTimeout(fadeTimer)
|
||||
|
||||
fadeTimer = setTimeout(() => {
|
||||
|
|
|
@ -61,7 +61,7 @@ export const EditorSwiper = (props: Props) => {
|
|||
createEffect(
|
||||
on(
|
||||
() => props.images.length,
|
||||
() => {
|
||||
(_) => {
|
||||
mainSwipeRef.current?.swiper.update()
|
||||
thumbSwipeRef.current?.swiper.update()
|
||||
},
|
||||
|
|
|
@ -45,7 +45,7 @@ export const ImageSwiper = (props: Props) => {
|
|||
createEffect(
|
||||
on(
|
||||
() => props.images.length,
|
||||
() => {
|
||||
(_) => {
|
||||
mainSwipeRef.current?.swiper.update()
|
||||
thumbSwipeRef.current?.swiper.update()
|
||||
},
|
||||
|
|
|
@ -94,20 +94,13 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
|
|||
|
||||
createEffect(
|
||||
on(
|
||||
() => author(),
|
||||
(a) => {
|
||||
if (a?.id) {
|
||||
try {
|
||||
const appdata = session()?.user.app_data
|
||||
if (appdata) {
|
||||
const { authors, followers, topics } = appdata
|
||||
setFollows({ authors, topics })
|
||||
setFollowers(followers)
|
||||
if (!authors) fetchData()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
() => session()?.user.app_data,
|
||||
(appdata) => {
|
||||
if (appdata) {
|
||||
const { authors, followers, topics } = appdata
|
||||
setFollows({ authors, topics })
|
||||
setFollowers(followers)
|
||||
if (!authors) fetchData()
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -96,15 +96,15 @@ export const SessionProvider = (props: {
|
|||
// handle auth state callback
|
||||
createEffect(
|
||||
on(
|
||||
() => searchParams()?.state,
|
||||
(state) => {
|
||||
if (state) {
|
||||
setOauthState((_s) => state)
|
||||
const scope = searchParams()?.scope
|
||||
? searchParams()?.scope?.toString().split(' ')
|
||||
searchParams,
|
||||
(params) => {
|
||||
if (params?.state) {
|
||||
setOauthState((_s) => params?.state)
|
||||
const scope = params?.scope
|
||||
? params?.scope?.toString().split(' ')
|
||||
: ['openid', 'profile', 'email']
|
||||
if (scope) console.info(`[context.session] scope: ${scope}`)
|
||||
const url = searchParams()?.redirect_uri || searchParams()?.redirectURL || window.location.href
|
||||
const url = params?.redirect_uri || params?.redirectURL || window.location.href
|
||||
setConfig((c: ConfigType) => ({ ...c, redirectURL: url.split('?')[0] }))
|
||||
changeSearchParams({ mode: 'confirm-email', m: 'auth' }, true)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup } from 'solid-js'
|
||||
|
||||
import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
|
@ -20,38 +20,19 @@ export const AuthorPage = (props: PageProps) => {
|
|||
Boolean(props.authorShouts) && Boolean(props.author) && props.author.slug === slug(),
|
||||
)
|
||||
|
||||
const preload = () => {
|
||||
return Promise.all([
|
||||
loadShouts({
|
||||
filters: { author: slug(), featured: false },
|
||||
limit: PRERENDERED_ARTICLES_COUNT,
|
||||
}),
|
||||
loadAuthor({ slug: slug() }),
|
||||
])
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
resetSortedArticles()
|
||||
await preload()
|
||||
|
||||
setIsLoaded(true)
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => slug(),
|
||||
async () => {
|
||||
on(slug, async (s) => {
|
||||
if (s) {
|
||||
setIsLoaded(false)
|
||||
resetSortedArticles()
|
||||
await preload()
|
||||
await loadShouts({
|
||||
filters: { author: s, featured: false },
|
||||
limit: PRERENDERED_ARTICLES_COUNT,
|
||||
})
|
||||
await loadAuthor({ slug: s })
|
||||
setIsLoaded(true)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
|
|
@ -12,10 +12,9 @@ import { LayoutType } from '../types'
|
|||
export const ExpoPage = (props: PageProps) => {
|
||||
const { t } = useLocalize()
|
||||
const { page } = useRouter()
|
||||
const getLayout = createMemo<LayoutType>(() => page().params['layout'] as LayoutType)
|
||||
|
||||
const getTitle = () => {
|
||||
switch (getLayout()) {
|
||||
const layout = createMemo(() => page().params['layout'] as LayoutType)
|
||||
const title = createMemo(() => {
|
||||
switch (layout()) {
|
||||
case 'audio': {
|
||||
return t('Audio')
|
||||
}
|
||||
|
@ -32,22 +31,14 @@ export const ExpoPage = (props: PageProps) => {
|
|||
return t('Art')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => getLayout(),
|
||||
() => {
|
||||
document.title = getTitle()
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
createEffect(on(title, (t) => (document.title = t), { defer: true }))
|
||||
|
||||
return (
|
||||
<PageLayout withPadding={true} zeroBottomPadding={true} title={getTitle()}>
|
||||
<PageLayout withPadding={true} zeroBottomPadding={true} title={title()}>
|
||||
<Topics />
|
||||
<Expo shouts={props.expoShouts} layout={getLayout()} />
|
||||
<Expo shouts={props.expoShouts} layout={layout()} />
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -25,20 +25,9 @@ const handleMyFeedLoadShouts = (options: LoadShoutsOptions) => {
|
|||
|
||||
export const FeedPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
const { page } = useRouter()
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => page().route,
|
||||
() => {
|
||||
resetSortedArticles()
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
createEffect(on(page, (_) => resetSortedArticles(), { defer: true }))
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Feed')}>
|
||||
|
|
|
@ -44,10 +44,10 @@ export const ProfileSecurityPage = () => {
|
|||
createEffect(
|
||||
on(
|
||||
() => session()?.user?.email,
|
||||
() => {
|
||||
(email) => {
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
['email']: session()?.user?.email,
|
||||
email,
|
||||
}))
|
||||
},
|
||||
),
|
||||
|
|
|
@ -37,19 +37,17 @@ export const TopicPage = (props: PageProps) => {
|
|||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => slug(),
|
||||
async () => {
|
||||
on(slug, async (s) => {
|
||||
if (s) {
|
||||
setIsLoaded(false)
|
||||
resetSortedArticles()
|
||||
await preload()
|
||||
setIsLoaded(true)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
onCleanup(() => resetSortedArticles())
|
||||
onCleanup(resetSortedArticles)
|
||||
|
||||
const usePrerenderedData = props.topic?.slug === slug()
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ const cssModuleHMR = () => {
|
|||
modules.forEach((module) => {
|
||||
if (module.id.includes('.scss') || module.id.includes('.css')) {
|
||||
module.isSelfAccepting = true
|
||||
module.accept()
|
||||
// module.accept()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue
Block a user