commentsByAuthor

This commit is contained in:
Untone 2024-09-03 13:21:59 +03:00
parent e176544e36
commit 7714977391
3 changed files with 69 additions and 22 deletions

View File

@ -8,8 +8,9 @@ import { useAuthors } from '~/context/authors'
import { SHOUTS_PER_PAGE, useFeed } from '~/context/feed' import { SHOUTS_PER_PAGE, useFeed } from '~/context/feed'
import { useFollowing } from '~/context/following' import { useFollowing } from '~/context/following'
import { useLocalize } from '~/context/localize' import { useLocalize } from '~/context/localize'
import { useReactions } from '~/context/reactions'
import { useSession } from '~/context/session' import { useSession } from '~/context/session'
import { loadShouts } from '~/graphql/api/public' import { loadReactions, loadShouts } from '~/graphql/api/public'
import { graphqlClientCreate } from '~/graphql/client' import { graphqlClientCreate } from '~/graphql/client'
import getAuthorFollowersQuery from '~/graphql/query/core/author-followers' import getAuthorFollowersQuery from '~/graphql/query/core/author-followers'
import getAuthorFollowsQuery from '~/graphql/query/core/author-follows' import getAuthorFollowsQuery from '~/graphql/query/core/author-follows'
@ -33,6 +34,7 @@ type AuthorViewProps = {
} }
export const PRERENDERED_ARTICLES_COUNT = 12 export const PRERENDERED_ARTICLES_COUNT = 12
const COMMENTS_PER_PAGE = 12
// const LOAD_MORE_PAGE_SIZE = 9 // const LOAD_MORE_PAGE_SIZE = 9
export const AuthorView = (props: AuthorViewProps) => { export const AuthorView = (props: AuthorViewProps) => {
@ -55,15 +57,12 @@ export const AuthorView = (props: AuthorViewProps) => {
const [following, changeFollowing] = createSignal<Array<Author | Topic>>([] as Array<Author | Topic>) // flat AuthorFollowsResult const [following, changeFollowing] = createSignal<Array<Author | Topic>>([] as 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 [followersLoaded, setFollowersLoaded] = createSignal(false)
const [followingsLoaded, setFollowingsLoaded] = createSignal(false)
// derivatives // derivatives
const me = createMemo<Author>(() => session()?.user?.app_data?.profile as Author) const me = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
// Переход по табам
createEffect(() => {
setCurrentTab(params.tab)
})
// Объединенный эффект для загрузки автора и его подписок // Объединенный эффект для загрузки автора и его подписок
createEffect(async () => { createEffect(async () => {
const meData = session()?.user?.app_data?.profile as Author const meData = session()?.user?.app_data?.profile as Author
@ -72,6 +71,7 @@ export const AuthorView = (props: AuthorViewProps) => {
if (slug && meData?.slug === slug) { if (slug && meData?.slug === slug) {
setAuthor(meData) setAuthor(meData)
setFollowers(myFollowers() || []) setFollowers(myFollowers() || [])
setFollowersLoaded(true)
changeFollowing([...(myFollows?.topics || []), ...(myFollows?.authors || [])]) changeFollowing([...(myFollows?.topics || []), ...(myFollows?.authors || [])])
} else if (slug && !author()) { } else if (slug && !author()) {
await loadAuthor({ slug }) await loadAuthor({ slug })
@ -84,11 +84,13 @@ export const AuthorView = (props: AuthorViewProps) => {
.toPromise() .toPromise()
const follows = followsResp?.data?.get_author_followers || {} const follows = followsResp?.data?.get_author_followers || {}
changeFollowing([...(follows?.authors || []), ...(follows?.topics || [])]) changeFollowing([...(follows?.authors || []), ...(follows?.topics || [])])
setFollowingsLoaded(true)
const followersResp = await client() const followersResp = await client()
?.query(getAuthorFollowersQuery, { slug: foundAuthor.slug }) ?.query(getAuthorFollowersQuery, { slug: foundAuthor.slug })
.toPromise() .toPromise()
setFollowers(followersResp?.data?.get_author_followers || []) setFollowers(followersResp?.data?.get_author_followers || [])
setFollowersLoaded(true)
} }
} }
}) })
@ -108,7 +110,7 @@ export const AuthorView = (props: AuthorViewProps) => {
}) })
const handleDeleteComment = (id: number) => { const handleDeleteComment = (id: number) => {
setCommented((prev) => prev.filter((comment) => comment.id !== id)) setCommented((prev) => (prev||[]).filter((comment) => comment.id !== id))
} }
const TabNavigator = () => ( const TabNavigator = () => (
@ -140,16 +142,13 @@ export const AuthorView = (props: AuthorViewProps) => {
const [loadMoreHidden, setLoadMoreHidden] = createSignal(false) const [loadMoreHidden, setLoadMoreHidden] = createSignal(false)
const loadMore = async () => { const loadMore = async () => {
saveScrollPosition() saveScrollPosition()
const amountBefore = feedByAuthor()?.[props.authorSlug]?.length || 0 const authorhoutsFetcher = loadShouts({
const topicShoutsFetcher = loadShouts({
filters: { author: props.authorSlug }, filters: { author: props.authorSlug },
limit: SHOUTS_PER_PAGE, limit: SHOUTS_PER_PAGE,
offset: amountBefore offset: feedByAuthor()?.[props.authorSlug]?.length || 0
}) })
const result = await topicShoutsFetcher() const result = await authorhoutsFetcher()
result && addFeed(result) result && addFeed(result)
const amountAfter = feedByAuthor()?.[props.authorSlug].length
setLoadMoreHidden(amountAfter === amountBefore)
restoreScrollPosition() restoreScrollPosition()
return result as LoadMoreItems return result as LoadMoreItems
} }
@ -161,10 +160,35 @@ export const AuthorView = (props: AuthorViewProps) => {
setSortedFeed(feed) setSortedFeed(feed)
},{})) },{}))
const [loadMoreCommentsHidden, setLoadMoreCommentsHidden] = createSignal(false)
const { commentsByAuthor, addReactions } = useReactions()
const loadMoreComments = async () => {
if (!author()) return [] as LoadMoreItems
saveScrollPosition()
const aid = author()?.id || 0
const authorCommentsFetcher = loadReactions({
by: {
comment: true,
author: author()?.slug
},
limit: COMMENTS_PER_PAGE,
offset: commentsByAuthor()[aid]?.length || 0
})
const result = await authorCommentsFetcher()
result && addReactions(result)
result && setCommented((prev) => [...new Set([...(prev||[]), ...result])])
restoreScrollPosition()
return result as LoadMoreItems
}
createEffect(() => setCurrentTab(params.tab))
createEffect(on([author, commented], ([a, ccc]) => a && setLoadMoreCommentsHidden(ccc?.length === a.stat?.comments), {}))
createEffect(on([author, feedByAuthor], ([a, feed]) => a && feed[props.authorSlug] && setLoadMoreHidden(feed[props.authorSlug]?.length === a.stat?.shouts), {}))
return ( return (
<div class={styles.authorPage}> <div class={styles.authorPage}>
<div class="wide-container"> <div class="wide-container">
<Show when={author()} fallback={<Loading />}> <Show when={author() && followersLoaded() && followingsLoaded()} fallback={<Loading />}>
<> <>
<div class={styles.authorHeader}> <div class={styles.authorHeader}>
<AuthorCard <AuthorCard
@ -221,6 +245,8 @@ export const AuthorView = (props: AuthorViewProps) => {
</div> </div>
</Show> </Show>
<LoadMoreWrapper loadFunction={loadMoreComments} pageSize={COMMENTS_PER_PAGE} hidden={loadMoreCommentsHidden()}>
<div class="wide-container"> <div class="wide-container">
<div class="row"> <div class="row">
<div class="col-md-20 col-lg-18"> <div class="col-md-20 col-lg-18">
@ -239,6 +265,7 @@ export const AuthorView = (props: AuthorViewProps) => {
</div> </div>
</div> </div>
</div> </div>
</LoadMoreWrapper>
</Match> </Match>
<Match when={!currentTab()}> <Match when={!currentTab()}>

View File

@ -49,7 +49,10 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => {
restoreScrollPosition() restoreScrollPosition()
} }
onMount(loadItems) onMount(() => {
loadItems()
console.debug(`load on mount ${items()}`)
})
return ( return (
<> <>

View File

@ -1,6 +1,6 @@
import type { JSX } from 'solid-js' import type { Accessor, JSX } from 'solid-js'
import { createContext, createMemo, onCleanup, useContext } from 'solid-js' import { createContext, createMemo, createSignal, onCleanup, useContext } from 'solid-js'
import { createStore, reconcile } from 'solid-js/store' import { createStore, reconcile } from 'solid-js/store'
import { coreApiUrl } from '~/config' import { coreApiUrl } from '~/config'
import { loadReactions } from '~/graphql/api/public' import { loadReactions } from '~/graphql/api/public'
@ -21,7 +21,8 @@ import { useSnackbar } from './ui'
type ReactionsContextType = { type ReactionsContextType = {
reactionEntities: Record<number, Reaction> reactionEntities: Record<number, Reaction>
reactionsByShout: Record<string, Reaction[]> reactionsByShout: Record<number, Reaction[]>
commentsByAuthor: Accessor<Record<number, Reaction[]>>
loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise<Reaction[]> loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise<Reaction[]>
createReaction: (reaction: MutationCreate_ReactionArgs) => Promise<void> createReaction: (reaction: MutationCreate_ReactionArgs) => Promise<void>
updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise<Reaction> updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise<Reaction>
@ -38,24 +39,40 @@ export function useReactions() {
export const ReactionsProvider = (props: { children: JSX.Element }) => { export const ReactionsProvider = (props: { children: JSX.Element }) => {
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({}) const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
const [reactionsByShout, setReactionsByShout] = createStore<Record<number, Reaction[]>>({}) const [reactionsByShout, setReactionsByShout] = createStore<Record<number, Reaction[]>>({})
const [reactionsByAuthor, setReactionsByAuthor] = createStore<Record<number, Reaction[]>>({})
const [commentsByAuthor, setCommentsByAuthor] = createSignal<Record<number, Reaction[]>>({})
const { t } = useLocalize() const { t } = useLocalize()
const { showSnackbar } = useSnackbar() const { showSnackbar } = useSnackbar()
const { session } = useSession() const { session } = useSession()
const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token))
const addReactions = (rrr: Reaction[]) => { const addReactions = (rrr: Reaction[]) => {
const newReactionsByShout: Record<string, Reaction[]> = { ...reactionsByShout } const newReactionsByShout: Record<number, Reaction[]> = { ...reactionsByShout }
const newReactionsByAuthor: Record<number, Reaction[]> = { ...reactionsByAuthor }
const newReactionEntities = rrr.reduce( const newReactionEntities = rrr.reduce(
(acc: { [reaction_id: number]: Reaction }, reaction: Reaction) => { (acc: { [reaction_id: number]: Reaction }, reaction: Reaction) => {
acc[reaction.id] = reaction acc[reaction.id] = reaction
if (!newReactionsByShout[reaction.shout.slug]) newReactionsByShout[reaction.shout.slug] = [] if (!newReactionsByShout[reaction.shout.id]) newReactionsByShout[reaction.shout.id] = []
newReactionsByShout[reaction.shout.slug].push(reaction) newReactionsByShout[reaction.shout.id].push(reaction)
if (!newReactionsByAuthor[reaction.created_by.id]) newReactionsByAuthor[reaction.created_by.id] = []
newReactionsByAuthor[reaction.created_by.id].push(reaction)
return acc return acc
}, },
{ ...reactionEntities } { ...reactionEntities }
) )
setReactionEntities(newReactionEntities) setReactionEntities(newReactionEntities)
setReactionsByShout(newReactionsByShout) setReactionsByShout(newReactionsByShout)
setReactionsByAuthor(newReactionsByAuthor)
const newCommentsByAuthor = Object.fromEntries(
Object.entries(newReactionsByAuthor).map(([authorId, reactions]) => [
authorId,
reactions.filter((x: Reaction) => x.kind === ReactionKind.Comment),
])
)
setCommentsByAuthor(newCommentsByAuthor)
} }
const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise<Reaction[]> => { const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise<Reaction[]> => {
@ -132,7 +149,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
addReactions addReactions
} }
const value: ReactionsContextType = { reactionEntities, reactionsByShout, ...actions } const value: ReactionsContextType = { reactionEntities, reactionsByShout, commentsByAuthor, ...actions }
return <ReactionsContext.Provider value={value}>{props.children}</ReactionsContext.Provider> return <ReactionsContext.Provider value={value}>{props.children}</ReactionsContext.Provider>
} }