diff --git a/client/reader/recent/index.tsx b/client/reader/recent/index.tsx index 53276435b55aa9..64eaa1d453c274 100644 --- a/client/reader/recent/index.tsx +++ b/client/reader/recent/index.tsx @@ -13,6 +13,7 @@ import NavigationHeader from 'calypso/components/navigation-header'; import { getPostByKey } from 'calypso/state/reader/posts/selectors'; import { requestPaginatedStream } from 'calypso/state/reader/streams/actions'; import { viewStream } from 'calypso/state/reader-ui/actions'; +import Skeleton from '../components/skeleton'; import EngagementBar from './engagement-bar'; import RecentPostField from './recent-post-field'; import RecentPostSkeleton from './recent-post-skeleton'; @@ -25,6 +26,15 @@ interface RecentProps { viewToggle?: React.ReactNode; } +interface PaddingItem { + isPadding: true; + postId: string; +} + +function isPaddingItem( item: ReaderPost | PaddingItem ): item is PaddingItem { + return 'isPadding' in item; +} + const Recent = ( { viewToggle }: RecentProps ) => { const dispatch = useDispatch< ThunkDispatch< AppState, void, UnknownAction > >(); const [ selectedItem, setSelectedItem ] = useState< ReaderPost | null >( null ); @@ -65,7 +75,10 @@ const Recent = ( { viewToggle }: RecentProps ) => { return {}; } - return items.reduce( ( acc: Record< string, PostItem >, item: ReaderPost ) => { + return items.reduce( ( acc: Record< string, PostItem >, item: ReaderPost | PaddingItem ) => { + if ( isPaddingItem( item ) ) { + return acc; + } const post = getPostByKey( state, { feedId: item.feedId, postId: item.postId, @@ -90,7 +103,10 @@ const Recent = ( { viewToggle }: RecentProps ) => { { id: 'icon', label: translate( 'Icon' ), - render: ( { item }: { item: ReaderPost } ) => { + render: ( { item }: { item: ReaderPost | PaddingItem } ) => { + if ( isPaddingItem( item ) ) { + return ; + } const post = getPostFromItem( item ); const iconUrl = post?.site_icon?.img || post?.author?.avatar_URL || ''; return iconUrl ? : null; @@ -101,9 +117,19 @@ const Recent = ( { viewToggle }: RecentProps ) => { { id: 'post', label: translate( 'Post' ), - getValue: ( { item }: { item: ReaderPost } ) => - `${ getPostFromItem( item )?.title ?? '' } - ${ item?.site_name ?? '' }`, - render: ( { item }: { item: ReaderPost } ) => { + getValue: ( { item }: { item: ReaderPost | PaddingItem } ) => + isPaddingItem( item ) + ? '' + : `${ getPostFromItem( item )?.title ?? '' } - ${ item?.site_name ?? '' }`, + render: ( { item }: { item: ReaderPost | PaddingItem } ) => { + if ( isPaddingItem( item ) ) { + return ( + <> + + + + ); + } return (
handleItemFocus( item.postId?.toString() ) }> { const focusedItem = shownData.find( ( item ) => item.postId?.toString() === focusedIndexRef.current ); - if ( focusedItem ) { + if ( focusedItem && ! isPaddingItem( focusedItem ) ) { setSelectedItem( focusedItem ); setTimeout( () => { postColumnRef.current?.focus(); diff --git a/client/reader/recent/style.scss b/client/reader/recent/style.scss index 1cef2a3df7df03..5690fd0f9db397 100644 --- a/client/reader/recent/style.scss +++ b/client/reader/recent/style.scss @@ -184,16 +184,6 @@ body.is-reader-full-post { padding: $recent-feed-spacing; } } - - // This is a temporary fix to disable the pagination page selector because of this issue: Automattic/loop#247 - // Once that issue is fixed, this code should be removed. - .dataviews-pagination__page-select { - display: none; - - & + div { - margin-left: auto; - } - } } &__post-column { diff --git a/client/state/data-layer/wpcom/read/streams/index.js b/client/state/data-layer/wpcom/read/streams/index.js index 040805ed36adc6..0e8ad564a32b6f 100644 --- a/client/state/data-layer/wpcom/read/streams/index.js +++ b/client/state/data-layer/wpcom/read/streams/index.js @@ -427,7 +427,7 @@ function get_page_handle( streamType, action, data ) { export function handlePage( action, data ) { const { posts, sites, cards } = data; - const { streamKey, query, isPoll, gap, streamType } = action.payload; + const { streamKey, query, isPoll, gap, streamType, page, perPage } = action.payload; const pageHandle = get_page_handle( streamType, action, data ); const { dateProperty } = streamApis[ streamType ]; @@ -502,6 +502,8 @@ export function handlePage( action, data ) { gap, totalItems, totalPages, + page, + perPage, } ) ); } diff --git a/client/state/reader/streams/actions.js b/client/state/reader/streams/actions.js index e52e4d22d50ec8..74aa891c768a54 100644 --- a/client/state/reader/streams/actions.js +++ b/client/state/reader/streams/actions.js @@ -47,7 +47,16 @@ export function requestPage( { }; } -export function receivePage( { streamKey, pageHandle, streamItems, gap, totalItems, totalPages } ) { +export function receivePage( { + streamKey, + pageHandle, + streamItems, + gap, + totalItems, + totalPages, + page, + perPage, +} ) { return { type: READER_STREAMS_PAGE_RECEIVE, payload: { @@ -57,6 +66,8 @@ export function receivePage( { streamKey, pageHandle, streamItems, gap, totalIte gap, totalItems, totalPages, + page, + perPage, }, }; } diff --git a/client/state/reader/streams/reducer.js b/client/state/reader/streams/reducer.js index 106bb46f81ff31..57fa9148db3acf 100644 --- a/client/state/reader/streams/reducer.js +++ b/client/state/reader/streams/reducer.js @@ -36,16 +36,52 @@ export const items = ( state = [], action ) => { let gap; let newState; let newXPosts; + let perPage; + let page; + let streamKey; switch ( action.type ) { case READER_STREAMS_PAGE_RECEIVE: gap = action.payload.gap; streamItems = action.payload.streamItems; + perPage = action.payload.perPage; + page = action.payload.page; + streamKey = action.payload.streamKey; if ( ! Array.isArray( streamItems ) ) { return state; } + // For the Recent feeds, we need to pad the stream with empty items + // for the DataViews pagination to work correctly + // see Automattic/loop#238 + if ( streamKey?.startsWith( 'recent' ) && streamItems.length > 0 && perPage && page > 1 ) { + // Calculate where new items should start + const startIndex = ( page - 1 ) * perPage; + const existingLength = state.length; + const paddingNeeded = startIndex - existingLength; + + // Case 1: Need to add padding before new items + if ( paddingNeeded > 0 ) { + const paddingItems = Array( paddingNeeded ) + .fill( undefined ) + .map( ( _, index ) => ( { + isPadding: true, + postId: `padding-${ index }`, + } ) ); + + return combineXPosts( [ ...state, ...paddingItems, ...streamItems ] ); + } + + // Case 2: Replace existing items at correct index + const updatedState = [ ...state ]; + streamItems.forEach( ( item, index ) => { + updatedState[ startIndex + index ] = item; + } ); + + return combineXPosts( updatedState ); + } + if ( gap ) { const beforeGap = takeWhile( state, ( postKey ) => ! keysAreEqual( postKey, gap ) ); const afterGap = takeRightWhile( state, ( postKey ) => ! keysAreEqual( postKey, gap ) );