diff --git a/src/views/ArticleDetail/AuthorSidebar/Collection/gql.ts b/src/views/ArticleDetail/AuthorSidebar/Collection/gql.ts index 3ab888e748..dc114d7af3 100644 --- a/src/views/ArticleDetail/AuthorSidebar/Collection/gql.ts +++ b/src/views/ArticleDetail/AuthorSidebar/Collection/gql.ts @@ -3,18 +3,35 @@ import gql from 'graphql-tag' import { ArticleDigestSidebar } from '~/components' export const AUTHOR_SIDEBAR_COLLECTION = gql` - query AuthorSidebarCollection($id: ID!, $after: String) { + query AuthorSidebarCollection( + $id: ID! + $before: String + $after: String + $includeBefore: Boolean + $includeAfter: Boolean + ) { node(input: { id: $id }) { id ... on Collection { id title description - articles(input: { first: 40, after: $after }) { + articles( + input: { + last: 20 + before: $before + first: 20 + after: $after + includeBefore: $includeBefore + includeAfter: $includeAfter + } + ) { totalCount pageInfo { + startCursor endCursor hasNextPage + hasPreviousPage } edges { cursor diff --git a/src/views/ArticleDetail/AuthorSidebar/Collection/index.tsx b/src/views/ArticleDetail/AuthorSidebar/Collection/index.tsx index c7a0e857c0..ec7d2778ea 100644 --- a/src/views/ArticleDetail/AuthorSidebar/Collection/index.tsx +++ b/src/views/ArticleDetail/AuthorSidebar/Collection/index.tsx @@ -1,6 +1,13 @@ +import { useEffect, useState } from 'react' import { FormattedMessage } from 'react-intl' -import { analytics, mergeConnections, toPath } from '~/common/utils' +import { ARTICLE_DIGEST_AUTHOR_SIDEBAR_ID_PREFIX } from '~/common/enums' +import { + analytics, + mergeConnections, + toPath, + unshiftConnections, +} from '~/common/utils' import { ArticleDigestAuthorSidebar, InfiniteScroll, @@ -9,6 +16,7 @@ import { QueryError, Throw404, usePublicQuery, + useRoute, } from '~/components' import { ArticleDetailPublicQuery, @@ -28,43 +36,120 @@ type CollectionProps = { } const Collection = ({ article, collectionId }: CollectionProps) => { + const { getQuery } = useRoute() + const cursor = getQuery('cursor') + const [isPrevLoading, setIsPrevLoading] = useState(false) + const [lastTopArticleId, setLastTopArticleId] = useState(null) + /** * Data Fetching */ - const { data, loading, error, fetchMore } = - usePublicQuery(AUTHOR_SIDEBAR_COLLECTION, { - variables: { id: collectionId }, - }) - const collection = data?.node! + const { + data: prevData, + loading: prevLoading, + error: prevError, + fetchMore: prevFetchMore, + } = usePublicQuery(AUTHOR_SIDEBAR_COLLECTION, { + variables: { id: collectionId, before: cursor, includeBefore: true }, + }) + + const { + data: afterData, + loading: afterLoading, + error: afterError, + fetchMore: afterFetchMore, + } = usePublicQuery(AUTHOR_SIDEBAR_COLLECTION, { + variables: { id: collectionId, after: cursor }, + }) + + const collection = prevData?.node! + const prevCollection = prevData?.node! + const afterCollection = afterData?.node! + + useEffect(() => { + if (!article) return + const articleDigestAuthorSidebar = document.getElementById( + `${ARTICLE_DIGEST_AUTHOR_SIDEBAR_ID_PREFIX}${article.id}` + ) + if (articleDigestAuthorSidebar) { + articleDigestAuthorSidebar.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }) + } + }, [article, prevLoading, afterLoading]) + + useEffect(() => { + if (!prevData?.node || prevData.node.__typename !== 'Collection') return + setLastTopArticleId(prevData.node.articles.edges?.[0]?.node.id || null) + }, [prevData]) /** * Render */ - if (loading) { + if (prevLoading || afterLoading) { return } - - if (error) { - return + if (prevError || afterError) { + return } - if (!collection || collection.__typename !== 'Collection') { + if ( + !collection || + collection.__typename !== 'Collection' || + !afterCollection || + afterCollection.__typename !== 'Collection' || + !prevCollection || + prevCollection.__typename !== 'Collection' + ) { return } // pagination const connectionPath = 'node.articles' - const { edges, pageInfo } = collection?.articles || {} + const { edges: prevEdges, pageInfo: prevPageInfo } = + prevCollection?.articles || {} + const { edges: afterEdges, pageInfo: afterPageInfo } = + afterCollection?.articles || {} + const edges = [...(prevEdges || []), ...(afterEdges || [])] + + // load previous page + const loadPreviousMore = async () => { + if (!prevPageInfo?.hasPreviousPage) { + return + } + + await prevFetchMore({ + variables: { before: prevPageInfo?.startCursor, includeBefore: false }, + updateQuery: (previousResult, { fetchMoreResult }) => + unshiftConnections({ + oldData: previousResult, + newData: fetchMoreResult, + path: connectionPath, + }), + }) + setIsPrevLoading(false) + + const lastTopArticleDigest = document.getElementById( + `${ARTICLE_DIGEST_AUTHOR_SIDEBAR_ID_PREFIX}${lastTopArticleId}` + ) + if (lastTopArticleDigest) { + lastTopArticleDigest.scrollIntoView({ + behavior: 'instant', + block: 'center', + }) + } + } // load next page - const loadMore = async () => { + const loadAfterMore = async () => { analytics.trackEvent('load_more', { type: 'article_detail_author_sidebar_collection', - location: edges?.length || 0, + location: afterEdges?.length || 0, }) - await fetchMore({ - variables: { after: pageInfo?.endCursor }, + await afterFetchMore({ + variables: { after: afterPageInfo?.endCursor }, updateQuery: (previousResult, { fetchMoreResult }) => mergeConnections({ oldData: previousResult, @@ -100,10 +185,23 @@ const Collection = ({ article, collectionId }: CollectionProps) => { )} -
+
{ + const element = event.currentTarget + if (element.scrollTop === 0) { + if (!prevPageInfo?.hasPreviousPage) { + return + } + setIsPrevLoading(true) + loadPreviousMore() + } + }} + > + {isPrevLoading && } } > @@ -113,6 +211,7 @@ const Collection = ({ article, collectionId }: CollectionProps) => { article={node} titleTextSize={14} collectionId={collectionId} + cursor={cursor} titleColor={node.id === article?.id ? 'black' : 'greyDarker'} showCover={false} clickEvent={() => {