Skip to content

Commit

Permalink
feat(ArticleDetail): add bi-directional scrolling in collection sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
Kechicode committed Dec 16, 2024
1 parent 1517167 commit a2a5b3b
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 21 deletions.
21 changes: 19 additions & 2 deletions src/views/ArticleDetail/AuthorSidebar/Collection/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
137 changes: 118 additions & 19 deletions src/views/ArticleDetail/AuthorSidebar/Collection/index.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -9,6 +16,7 @@ import {
QueryError,
Throw404,
usePublicQuery,
useRoute,
} from '~/components'
import {
ArticleDetailPublicQuery,
Expand All @@ -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<string | null>(null)

/**
* Data Fetching
*/
const { data, loading, error, fetchMore } =
usePublicQuery<AuthorSidebarCollectionQuery>(AUTHOR_SIDEBAR_COLLECTION, {
variables: { id: collectionId },
})
const collection = data?.node!
const {
data: prevData,
loading: prevLoading,
error: prevError,
fetchMore: prevFetchMore,
} = usePublicQuery<AuthorSidebarCollectionQuery>(AUTHOR_SIDEBAR_COLLECTION, {
variables: { id: collectionId, before: cursor, includeBefore: true },
})

const {
data: afterData,
loading: afterLoading,
error: afterError,
fetchMore: afterFetchMore,
} = usePublicQuery<AuthorSidebarCollectionQuery>(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 <FeedPlaceholder />
}

if (error) {
return <QueryError error={error} />
if (prevError || afterError) {
return <QueryError error={prevError ?? afterError!} />
}

if (!collection || collection.__typename !== 'Collection') {
if (
!collection ||
collection.__typename !== 'Collection' ||
!afterCollection ||
afterCollection.__typename !== 'Collection' ||
!prevCollection ||
prevCollection.__typename !== 'Collection'
) {
return <Throw404 />
}

// 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,
Expand Down Expand Up @@ -100,10 +185,23 @@ const Collection = ({ article, collectionId }: CollectionProps) => {
</section>
</LinkWrapper>
)}
<section className={styles.feed}>
<section
className={styles.feed}
onScroll={(event) => {
const element = event.currentTarget
if (element.scrollTop === 0) {
if (!prevPageInfo?.hasPreviousPage) {
return
}
setIsPrevLoading(true)
loadPreviousMore()
}
}}
>
{isPrevLoading && <ArticleDigestAuthorSidebarFeedPlaceholder />}
<InfiniteScroll
hasNextPage={pageInfo?.hasNextPage}
loadMore={loadMore}
hasNextPage={afterPageInfo?.hasNextPage}
loadMore={loadAfterMore}
loader={<ArticleDigestAuthorSidebarFeedPlaceholder />}
>
<List borderPosition="top">
Expand All @@ -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={() => {
Expand Down

0 comments on commit a2a5b3b

Please sign in to comment.