subs-refactoring

This commit is contained in:
Untone 2024-05-21 02:15:52 +03:00
parent bec333f7c3
commit 79f94876f0
24 changed files with 253 additions and 336 deletions

View File

@ -423,14 +423,14 @@
"subscriber": "subscriber", "subscriber": "subscriber",
"subscriber_rp": "subscriber", "subscriber_rp": "subscriber",
"subscribers": "subscribers", "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...", "subscribing...": "subscribing...",
"subscription": "subscription", "subscription": "subscription",
"Subscription": "Subscription", "Subscription": "Subscription",
"subscription_rp": "subscription", "subscription_rp": "subscription",
"subscriptions": "subscriptions", "subscriptions": "subscriptions",
"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", "Substrate": "Substrate",
"Success": "Success", "Success": "Success",
"Successfully authorized": "Authorization successful", "Successfully authorized": "Authorization successful",
@ -541,4 +541,4 @@
"You've reached a non-existed page": "You've reached a non-existed page", "You've reached a non-existed page": "You've reached a non-existed page",
"Your email": "Your email", "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" "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"
} }

View File

@ -451,11 +451,11 @@
"subscriber": "подписчик", "subscriber": "подписчик",
"subscriber_rp": "подписчика", "subscriber_rp": "подписчика",
"subscribers": "подписчиков", "subscribers": "подписчиков",
"SubscriberWithCount": "{count, plural, =0 {нет подписчиков} one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}}", "FollowersWithCount": "{count, plural, =0 {нет подписчиков} one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}}",
"subscribing...": "Подписка...", "subscribing...": "Подписка...",
"Subscription": "Подписка", "Subscription": "Подписка",
"Subscriptions": "Подписки", "Subscriptions": "Подписки",
"SubscriptionWithCount": "{count, plural, =0 {нет подписок} one {{count} подписка} few {{count} подписки} other {{count} подписок}}", "FollowsWithCount": "{count, plural, =0 {нет подписок} one {{count} подписка} few {{count} подписки} other {{count} подписок}}",
"Substrate": "Подложка", "Substrate": "Подложка",
"Success": "Успешно", "Success": "Успешно",
"Successfully authorized": "Авторизация успешна", "Successfully authorized": "Авторизация успешна",
@ -568,4 +568,4 @@
"You've successfully logged out": "Вы успешно вышли из аккаунта", "You've successfully logged out": "Вы успешно вышли из аккаунта",
"Your email": "Ваш email", "Your email": "Ваш 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.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах"
} }

View File

@ -31,16 +31,7 @@ export const AudioPlayer = (props: Props) => {
const [isPlaying, setIsPlaying] = createSignal(false) const [isPlaying, setIsPlaying] = createSignal(false)
const currentTack = createMemo(() => props.media[currentTrackIndex()]) 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) => { const handlePlayMedia = async (trackIndex: number) => {
setIsPlaying(!isPlaying() || trackIndex !== currentTrackIndex()) setIsPlaying(!isPlaying() || trackIndex !== currentTrackIndex())

View File

@ -1,6 +1,6 @@
import { openPage } from '@nanostores/router' import { openPage } from '@nanostores/router'
import { clsx } from 'clsx' 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 { useFollowing } from '../../../context/following'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
@ -34,17 +34,19 @@ export const AuthorBadge = (props: Props) => {
const { author, requireAuthentication } = useSession() const { author, requireAuthentication } = useSession()
const { follow, unfollow, follows, following } = useFollowing() const { follow, unfollow, follows, following } = useFollowing()
const [isMobileView, setIsMobileView] = createSignal(false) const [isMobileView, setIsMobileView] = createSignal(false)
const [isFollowed, setIsFollowed] = createSignal<boolean>() const [isFollowed, setIsFollowed] = createSignal<boolean>(
follows?.authors?.some((authorEntity) => authorEntity.id === props.author?.id),
createEffect(() => { )
if (!(follows && props.author)) return createEffect(() => setIsMobileView(!mediaMatches.sm))
const followed = follows?.authors?.some((authorEntity) => authorEntity.id === props.author?.id) createEffect(
setIsFollowed(followed) on(
}) [() => follows?.authors, () => props.author, following],
([followingAuthors, currentAuthor, _]) => {
createEffect(() => { setIsFollowed(followingAuthors?.some((followedAuthor) => followedAuthor.id === currentAuthor?.id))
setIsMobileView(!mediaMatches.sm) },
}) { defer: true },
),
)
const { changeSearchParams } = useRouter() const { changeSearchParams } = useRouter()
const { t, formatDate, lang } = useLocalize() const { t, formatDate, lang } = useLocalize()
@ -132,7 +134,7 @@ export const AuthorBadge = (props: Props) => {
<Show when={props.author.slug !== author()?.slug && !props.nameOnly}> <Show when={props.author.slug !== author()?.slug && !props.nameOnly}>
<div class={styles.actions}> <div class={styles.actions}>
<FollowingButton <FollowingButton
action={() => handleFollowClick()} action={handleFollowClick}
isFollowed={isFollowed()} isFollowed={isFollowed()}
actionMessageType={following()?.slug === props.author.slug ? following().type : undefined} actionMessageType={following()?.slug === props.author.slug ? following().type : undefined}
/> />

View File

@ -429,7 +429,7 @@
} }
} }
.subscribersContainer { .followersContainer {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 1.4rem; font-size: 1.4rem;
@ -440,7 +440,7 @@
} }
} }
.subscribers { .followers {
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
display: inline-flex; display: inline-flex;
@ -456,7 +456,7 @@
margin-right: 0; margin-right: 0;
} }
.subscribersItem { .followersItem {
position: relative; position: relative;
&:nth-child(1) { &:nth-child(1) {
@ -473,7 +473,7 @@
} }
} }
.subscribersCounter { .followsCounter {
font-weight: 500; font-weight: 500;
margin-left: 1rem; margin-left: 1rem;
} }
@ -481,7 +481,7 @@
&:hover { &:hover {
background: none !important; background: none !important;
.subscribersCounter { .followsCounter {
background: var(--background-color-invert); background: var(--background-color-invert);
} }
} }
@ -489,4 +489,4 @@
.listWrapper { .listWrapper {
max-height: 70vh; max-height: 70vh;
} }

View File

@ -27,7 +27,7 @@ import styles from './AuthorCard.module.scss'
type Props = { type Props = {
author: Author author: Author
followers?: Author[] followers?: Author[]
following?: Array<Author | Topic> flatFollows?: Array<Author | Topic>
} }
export const AuthorCard = (props: Props) => { export const AuthorCard = (props: Props) => {
const { t, lang } = useLocalize() const { t, lang } = useLocalize()
@ -39,7 +39,7 @@ export const AuthorCard = (props: Props) => {
const { follow, unfollow, follows, following } = useFollowing() const { follow, unfollow, follows, following } = useFollowing()
onMount(() => { onMount(() => {
setAuthorSubs(props.following) setAuthorSubs(props.flatFollows)
}) })
createEffect(() => { createEffect(() => {
@ -71,15 +71,15 @@ export const AuthorCard = (props: Props) => {
} }
createEffect(() => { createEffect(() => {
if (props.following) { if (props.flatFollows) {
if (followsFilter() === 'authors') { if (followsFilter() === 'authors') {
setAuthorSubs(props.following.filter((s) => 'name' in s)) setAuthorSubs(props.flatFollows.filter((s) => 'name' in s))
} else if (followsFilter() === 'topics') { } else if (followsFilter() === 'topics') {
setAuthorSubs(props.following.filter((s) => 'title' in s)) setAuthorSubs(props.flatFollows.filter((s) => 'title' in s))
} else if (followsFilter() === 'communities') { } else if (followsFilter() === 'communities') {
setAuthorSubs(props.following.filter((s) => 'title' in s)) setAuthorSubs(props.flatFollows.filter((s) => 'title' in s))
} else { } else {
setAuthorSubs(props.following) setAuthorSubs(props.flatFollows)
} }
} }
}) })
@ -108,6 +108,73 @@ export const AuthorCard = (props: Props) => {
return t('Follow') 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 ( return (
<div class={clsx(styles.author, 'row')}> <div class={clsx(styles.author, 'row')}>
<div class="col-md-5"> <div class="col-md-5">
@ -125,35 +192,30 @@ export const AuthorCard = (props: Props) => {
<Show when={props.author.bio}> <Show when={props.author.bio}>
<div class={styles.authorAbout} innerHTML={props.author.bio} /> <div class={styles.authorAbout} innerHTML={props.author.bio} />
</Show> </Show>
<Show when={props.followers?.length > 0 || props.following?.length > 0}> <Show when={props.followers?.length > 0 || props.flatFollows?.length > 0}>
<div class={styles.subscribersContainer}> <div class={styles.followersContainer}>
<Show when={props.followers && props.followers.length > 0}> <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)}> <For each={props.followers.slice(0, 3)}>
{(f) => ( {(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> </For>
<div class={styles.subscribersCounter}> <div class={styles.followsCounter}>
{t('SubscriberWithCount', { {t('FollowersWithCount', {
count: props.followers.length ?? 0, count: props.followers.length ?? 0,
})} })}
</div> </div>
</a> </a>
</Show> </Show>
<Show when={props.following && props.following.length > 0}> <Show when={props.flatFollows?.length > 0}>
<a href="?m=following" class={styles.subscribers}> <a href="?m=following" class={styles.followers}>
<For each={props.following.slice(0, 3)}> <For each={props.flatFollows.slice(0, 3)}>
{(f) => { {(f) => {
if ('name' in f) { if ('name' in f) {
return ( return (
<Userpic <Userpic size={'XS'} name={f.name} userpic={f.pic} class={styles.followersItem} />
size={'XS'}
name={f.name}
userpic={f.pic}
class={styles.subscribersItem}
/>
) )
} }
@ -163,7 +225,7 @@ export const AuthorCard = (props: Props) => {
size={'XS'} size={'XS'}
name={f.title} name={f.title}
userpic={f.pic} userpic={f.pic}
class={styles.subscribersItem} class={styles.followersItem}
/> />
) )
} }
@ -171,9 +233,9 @@ export const AuthorCard = (props: Props) => {
return null return null
}} }}
</For> </For>
<div class={styles.subscribersCounter}> <div class={styles.followsCounter}>
{t('SubscriptionWithCount', { {t('FollowsWithCount', {
count: props?.following.length ?? 0, count: props?.flatFollows.length ?? 0,
})} })}
</div> </div>
</a> </a>
@ -251,77 +313,12 @@ export const AuthorCard = (props: Props) => {
</ShowOnlyOnClient> </ShowOnlyOnClient>
<Show when={props.followers}> <Show when={props.followers}>
<Modal variant="medium" isResponsive={true} name="followers" maxHeight> <Modal variant="medium" isResponsive={true} name="followers" maxHeight>
<> <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>
</>
</Modal> </Modal>
</Show> </Show>
<Show when={props.following}> <Show when={props.flatFollows}>
<Modal variant="medium" isResponsive={true} name="following" maxHeight> <Modal variant="medium" isResponsive={true} name="following" maxHeight>
<> <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.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>
</>
</Modal> </Modal>
</Show> </Show>
</div> </div>

View File

@ -63,18 +63,8 @@ export const PasswordField = (props: Props) => {
} }
} }
createEffect( createEffect(on(error, (er) => er && props.errorMessage?.(er), { defer: true }))
on( createEffect(() => setError(props.setError))
() => error(),
() => {
props.errorMessage?.(error())
},
{ defer: true },
),
)
createEffect(() => {
setError(props.setError)
})
return ( return (
<div class={clsx(styles.PassportField, props.class)}> <div class={clsx(styles.PassportField, props.class)}>

View File

@ -62,7 +62,7 @@ export const TableOfContents = (props: Props) => {
createEffect( createEffect(
on( on(
() => props.body, () => props.body,
() => debouncedUpdateHeadings(), (_) => debouncedUpdateHeadings(),
), ),
) )

View File

@ -40,65 +40,19 @@ const LOAD_MORE_PAGE_SIZE = 9
export const AuthorView = (props: Props) => { export const AuthorView = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
const { followers: myFollowers, follows: myFollows } = useFollowing() const { followers: myFollowers, follows: myFollows } = useFollowing()
const { session, author: me } = useSession() const { author: me } = useSession()
const { sortedArticles } = useArticlesStore({ shouts: props.shouts }) const { sortedArticles } = useArticlesStore({ shouts: props.shouts })
const { page: getPage, searchParams } = useRouter() const { page: getPage, searchParams } = useRouter()
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const [isBioExpanded, setIsBioExpanded] = createSignal(false) const [isBioExpanded, setIsBioExpanded] = createSignal(false)
const [author, setAuthor] = createSignal<Author>() const [author, setAuthor] = createSignal<Author>(props.author)
const [followers, setFollowers] = createSignal([]) const [followers, setFollowers] = createSignal([])
const [following, changeFollowing] = createSignal<Array<Author | Topic>>([]) // flat AuthorFollowsResult const [following, changeFollowing] = createSignal<Array<Author | Topic>>([]) // flat AuthorFollowsResult
const [showExpandBioControl, setShowExpandBioControl] = createSignal(false) const [showExpandBioControl, setShowExpandBioControl] = createSignal(false)
const [commented, setCommented] = createSignal<Reaction[]>() const [commented, setCommented] = createSignal<Reaction[]>()
const modal = MODALS[searchParams().m] 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 () => { const loadMore = async () => {
saveScrollPosition() saveScrollPosition()
const { hasMore } = await loadShouts({ const { hasMore } = await loadShouts({
@ -110,36 +64,72 @@ export const AuthorView = (props: Props) => {
restoreScrollPosition() 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(() => { onMount(() => {
if (!modal) hideModal() if (!modal) hideModal()
fetchData(props.authorSlug)
checkBioHeight() checkBioHeight()
loadMore()
}) })
const pages = createMemo<Shout[][]>(() => const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE), 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(() => const ogImage = createMemo(() =>
author()?.pic author()?.pic
? getImageUrl(author()?.pic, { width: 1200 }) ? getImageUrl(author()?.pic, { width: 1200 })
@ -168,7 +158,7 @@ export const AuthorView = (props: Props) => {
<Show when={author()} fallback={<Loading />}> <Show when={author()} fallback={<Loading />}>
<> <>
<div class={styles.authorHeader}> <div class={styles.authorHeader}>
<AuthorCard author={author()} followers={followers() || []} following={following() || []} /> <AuthorCard author={author()} followers={followers() || []} flatFollows={following() || []} />
</div> </div>
<div class={clsx(styles.groupControls, 'row')}> <div class={clsx(styles.groupControls, 'row')}>
<div class="col-md-16"> <div class="col-md-16">

View File

@ -143,16 +143,20 @@ export const FeedView = (props: Props) => {
Promise.all([loadTopComments()]).finally(() => setIsRightColumnLoaded(true)) Promise.all([loadTopComments()]).finally(() => setIsRightColumnLoaded(true))
}) })
createEffect(() => { createEffect(
if (session()?.access_token && !unratedArticles()) { on(
loadUnratedArticles() [() => session(), unratedArticles],
} ([s, seen]) => {
}) if (s?.access_token && !(seen?.length > 0)) loadUnratedArticles()
},
{ defer: true },
),
)
createEffect( createEffect(
on( on(
() => page().route + searchParams().by + searchParams().period + searchParams().visibility, [page, searchParams],
() => { (_, _p) => {
resetSortedArticles() resetSortedArticles()
loadMore() loadMore()
}, },

View File

@ -136,21 +136,18 @@ export const InboxView = (props: Props) => {
} }
createEffect( createEffect(
on( on(messages, () => {
() => messages(), if (!messagesContainerRef.current) {
() => { return
if (!messagesContainerRef.current) { }
return if (messagesContainerRef.current.scrollTop >= messagesContainerRef.current.scrollHeight) {
} return
if (messagesContainerRef.current.scrollTop >= messagesContainerRef.current.scrollHeight) { }
return messagesContainerRef.current.scroll({
} top: messagesContainerRef.current.scrollHeight,
messagesContainerRef.current.scroll({ behavior: 'smooth',
top: messagesContainerRef.current.scrollHeight, })
behavior: 'smooth', }),
})
},
),
{ defer: true }, { defer: true },
) )
const handleScrollMessageContainer = () => { const handleScrollMessageContainer = () => {

View File

@ -87,13 +87,7 @@ export const TopicView = (props: Props) => {
loadReactedTopMonthArticles(topic()?.slug) loadReactedTopMonthArticles(topic()?.slug)
} }
createEffect( createEffect(on(topic, loadRandom, { defer: true }))
on(
() => topic(),
() => loadRandom(),
{ defer: true },
),
)
const title = createMemo( const title = createMemo(
() => () =>

View File

@ -66,7 +66,7 @@ export const InviteMembers = (props: Props) => {
createEffect( createEffect(
on( on(
() => sortedAuthors(), sortedAuthors,
(currentAuthors) => { (currentAuthors) => {
setAuthorsToInvite(currentAuthors.map((author) => ({ ...author, selected: false }))) setAuthorsToInvite(currentAuthors.map((author) => ({ ...author, selected: false })))
}, },

View File

@ -129,8 +129,8 @@ export const Lightbox = (props: Props) => {
createEffect( createEffect(
on( on(
() => zoomLevel(), zoomLevel,
() => { (_) => {
clearTimeout(fadeTimer) clearTimeout(fadeTimer)
fadeTimer = setTimeout(() => { fadeTimer = setTimeout(() => {

View File

@ -61,7 +61,7 @@ export const EditorSwiper = (props: Props) => {
createEffect( createEffect(
on( on(
() => props.images.length, () => props.images.length,
() => { (_) => {
mainSwipeRef.current?.swiper.update() mainSwipeRef.current?.swiper.update()
thumbSwipeRef.current?.swiper.update() thumbSwipeRef.current?.swiper.update()
}, },

View File

@ -45,7 +45,7 @@ export const ImageSwiper = (props: Props) => {
createEffect( createEffect(
on( on(
() => props.images.length, () => props.images.length,
() => { (_) => {
mainSwipeRef.current?.swiper.update() mainSwipeRef.current?.swiper.update()
thumbSwipeRef.current?.swiper.update() thumbSwipeRef.current?.swiper.update()
}, },

View File

@ -94,20 +94,13 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
createEffect( createEffect(
on( on(
() => author(), () => session()?.user.app_data,
(a) => { (appdata) => {
if (a?.id) { if (appdata) {
try { const { authors, followers, topics } = appdata
const appdata = session()?.user.app_data setFollows({ authors, topics })
if (appdata) { setFollowers(followers)
const { authors, followers, topics } = appdata if (!authors) fetchData()
setFollows({ authors, topics })
setFollowers(followers)
if (!authors) fetchData()
}
} catch (e) {
console.error(e)
}
} }
}, },
), ),

View File

@ -96,15 +96,15 @@ export const SessionProvider = (props: {
// handle auth state callback // handle auth state callback
createEffect( createEffect(
on( on(
() => searchParams()?.state, searchParams,
(state) => { (params) => {
if (state) { if (params?.state) {
setOauthState((_s) => state) setOauthState((_s) => params?.state)
const scope = searchParams()?.scope const scope = params?.scope
? searchParams()?.scope?.toString().split(' ') ? params?.scope?.toString().split(' ')
: ['openid', 'profile', 'email'] : ['openid', 'profile', 'email']
if (scope) console.info(`[context.session] scope: ${scope}`) 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] })) setConfig((c: ConfigType) => ({ ...c, redirectURL: url.split('?')[0] }))
changeSearchParams({ mode: 'confirm-email', m: 'auth' }, true) changeSearchParams({ mode: 'confirm-email', m: 'auth' }, true)
} }

View File

@ -1,6 +1,6 @@
import type { PageProps } from './types' 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 { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author'
import { Loading } from '../components/_shared/Loading' 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(), 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( createEffect(
on( on(slug, async (s) => {
() => slug(), if (s) {
async () => {
setIsLoaded(false) setIsLoaded(false)
resetSortedArticles() resetSortedArticles()
await preload() await loadShouts({
filters: { author: s, featured: false },
limit: PRERENDERED_ARTICLES_COUNT,
})
await loadAuthor({ slug: s })
setIsLoaded(true) setIsLoaded(true)
}, }
{ defer: true }, }),
),
) )
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())

View File

@ -12,10 +12,9 @@ import { LayoutType } from '../types'
export const ExpoPage = (props: PageProps) => { export const ExpoPage = (props: PageProps) => {
const { t } = useLocalize() const { t } = useLocalize()
const { page } = useRouter() const { page } = useRouter()
const getLayout = createMemo<LayoutType>(() => page().params['layout'] as LayoutType) const layout = createMemo(() => page().params['layout'] as LayoutType)
const title = createMemo(() => {
const getTitle = () => { switch (layout()) {
switch (getLayout()) {
case 'audio': { case 'audio': {
return t('Audio') return t('Audio')
} }
@ -32,22 +31,14 @@ export const ExpoPage = (props: PageProps) => {
return t('Art') return t('Art')
} }
} }
} })
createEffect( createEffect(on(title, (t) => (document.title = t), { defer: true }))
on(
() => getLayout(),
() => {
document.title = getTitle()
},
{ defer: true },
),
)
return ( return (
<PageLayout withPadding={true} zeroBottomPadding={true} title={getTitle()}> <PageLayout withPadding={true} zeroBottomPadding={true} title={title()}>
<Topics /> <Topics />
<Expo shouts={props.expoShouts} layout={getLayout()} /> <Expo shouts={props.expoShouts} layout={layout()} />
</PageLayout> </PageLayout>
) )
} }

View File

@ -25,20 +25,9 @@ const handleMyFeedLoadShouts = (options: LoadShoutsOptions) => {
export const FeedPage = () => { export const FeedPage = () => {
const { t } = useLocalize() const { t } = useLocalize()
onCleanup(() => resetSortedArticles())
const { page } = useRouter() const { page } = useRouter()
createEffect(on(page, (_) => resetSortedArticles(), { defer: true }))
createEffect( onCleanup(() => resetSortedArticles())
on(
() => page().route,
() => {
resetSortedArticles()
},
{ defer: true },
),
)
return ( return (
<PageLayout title={t('Feed')}> <PageLayout title={t('Feed')}>

View File

@ -44,10 +44,10 @@ export const ProfileSecurityPage = () => {
createEffect( createEffect(
on( on(
() => session()?.user?.email, () => session()?.user?.email,
() => { (email) => {
setFormData((prevData) => ({ setFormData((prevData) => ({
...prevData, ...prevData,
['email']: session()?.user?.email, email,
})) }))
}, },
), ),

View File

@ -37,19 +37,17 @@ export const TopicPage = (props: PageProps) => {
}) })
createEffect( createEffect(
on( on(slug, async (s) => {
() => slug(), if (s) {
async () => {
setIsLoaded(false) setIsLoaded(false)
resetSortedArticles() resetSortedArticles()
await preload() await preload()
setIsLoaded(true) setIsLoaded(true)
}, }
{ defer: true }, }),
),
) )
onCleanup(() => resetSortedArticles()) onCleanup(resetSortedArticles)
const usePrerenderedData = props.topic?.slug === slug() const usePrerenderedData = props.topic?.slug === slug()

View File

@ -16,7 +16,7 @@ const cssModuleHMR = () => {
modules.forEach((module) => { modules.forEach((module) => {
if (module.id.includes('.scss') || module.id.includes('.css')) { if (module.id.includes('.scss') || module.id.includes('.css')) {
module.isSelfAccepting = true module.isSelfAccepting = true
module.accept() // module.accept()
} }
}) })
}, },