From 4cda1e2866d7b5d4471761b1e457bed5f51116ca Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 18 Oct 2024 19:32:56 -0300 Subject: [PATCH] feat: create pagination with Link Headers for trending statuses --- src/actions/trending-statuses.ts | 55 +++++++++++++++++-- .../compose/components/search-results.tsx | 13 ++++- src/reducers/trending-statuses.ts | 10 +++- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/actions/trending-statuses.ts b/src/actions/trending-statuses.ts index e22448784..c6a26e316 100644 --- a/src/actions/trending-statuses.ts +++ b/src/actions/trending-statuses.ts @@ -1,6 +1,7 @@ +import { APIEntity } from 'soapbox/types/entities'; import { getFeatures } from 'soapbox/utils/features'; -import api from '../api'; +import api, { getLinks } from '../api'; import { importFetchedStatuses } from './importer'; @@ -9,6 +10,8 @@ import type { AppDispatch, RootState } from 'soapbox/store'; const TRENDING_STATUSES_FETCH_REQUEST = 'TRENDING_STATUSES_FETCH_REQUEST'; const TRENDING_STATUSES_FETCH_SUCCESS = 'TRENDING_STATUSES_FETCH_SUCCESS'; const TRENDING_STATUSES_FETCH_FAIL = 'TRENDING_STATUSES_FETCH_FAIL'; +const TRENDING_STATUSES_EXPAND_FAIL = 'TRENDING_STATUSES_EXPAND_FAIL'; +const TRENDING_STATUSES_EXPAND_SUCCESS = 'TRENDING_STATUSES_EXPAND_SUCCESS'; const fetchTrendingStatuses = () => (dispatch: AppDispatch, getState: () => RootState) => { @@ -20,18 +23,62 @@ const fetchTrendingStatuses = () => if (!features.trendingStatuses) return; dispatch({ type: TRENDING_STATUSES_FETCH_REQUEST }); - return api(getState).get('/api/v1/trends/statuses').then(({ data: statuses }) => { + return api(getState).get('/api/v1/trends/statuses').then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + const statuses = response.data; + dispatch(importFetchedStatuses(statuses)); - dispatch({ type: TRENDING_STATUSES_FETCH_SUCCESS, statuses }); + dispatch(fetchTrendingStatusesSuccess(statuses, next ? next.uri : null)); return statuses; }).catch(error => { - dispatch({ type: TRENDING_STATUSES_FETCH_FAIL, error }); + dispatch(fetchTrendingStatusesFail(error)); }); }; +const fetchTrendingStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({ + type: TRENDING_STATUSES_FETCH_SUCCESS, + statuses, + next, +}); + + +const fetchTrendingStatusesFail = (error: unknown) => ({ + type: TRENDING_STATUSES_FETCH_FAIL, + error, +}); + +const expandTrendingStatuses = (path: string) => + (dispatch: AppDispatch, getState: () => RootState) => { + api(getState).get(path).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + const statuses = response.data; + + dispatch(importFetchedStatuses(statuses)); + dispatch(expandTrendingStatusesSuccess(statuses, next ? next.uri : null)); + }).catch(error => { + dispatch(expandTrendingStatusesFail(error)); + }); + }; + +const expandTrendingStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({ + type: TRENDING_STATUSES_EXPAND_SUCCESS, + statuses, + next, +}); + +const expandTrendingStatusesFail = (error: unknown) => ({ + type: TRENDING_STATUSES_EXPAND_FAIL, + error, +}); + export { TRENDING_STATUSES_FETCH_REQUEST, TRENDING_STATUSES_FETCH_SUCCESS, TRENDING_STATUSES_FETCH_FAIL, + TRENDING_STATUSES_EXPAND_SUCCESS, + TRENDING_STATUSES_EXPAND_FAIL, fetchTrendingStatuses, + expandTrendingStatuses, }; diff --git a/src/features/compose/components/search-results.tsx b/src/features/compose/components/search-results.tsx index 18bfa496c..edf6e859d 100644 --- a/src/features/compose/components/search-results.tsx +++ b/src/features/compose/components/search-results.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { expandSearch, setFilter, setSearchAccount } from 'soapbox/actions/search'; -import { fetchTrendingStatuses } from 'soapbox/actions/trending-statuses'; +import { expandTrendingStatuses, fetchTrendingStatuses } from 'soapbox/actions/trending-statuses'; import { useAccount } from 'soapbox/api/hooks'; import Hashtag from 'soapbox/components/hashtag'; import IconButton from 'soapbox/components/icon-button'; @@ -36,13 +36,20 @@ const SearchResults = () => { const results = useAppSelector((state) => state.search.results); const suggestions = useAppSelector((state) => state.suggestions.items); const trendingStatuses = useAppSelector((state) => state.trending_statuses.items); + const nextTrendingStatuses = useAppSelector((state) => state.trending_statuses.next); const trends = useAppSelector((state) => state.trends.items); const submitted = useAppSelector((state) => state.search.submitted); const selectedFilter = useAppSelector((state) => state.search.filter); const filterByAccount = useAppSelector((state) => state.search.accountId || undefined); const { account } = useAccount(filterByAccount); - const handleLoadMore = () => dispatch(expandSearch(selectedFilter)); + const handleLoadMore = () => { + if (results.accounts.size || results.statuses.size || results.hashtags.size) { + dispatch(expandSearch(selectedFilter)); + } else if (nextTrendingStatuses) { + dispatch(expandTrendingStatuses(nextTrendingStatuses)); + } + }; const handleUnsetAccount = () => dispatch(setSearchAccount(null)); @@ -224,7 +231,7 @@ const SearchResults = () => { scrollKey={`${selectedFilter}:${value}`} isLoading={submitted && !loaded} showLoading={submitted && !loaded && searchResults?.isEmpty()} - hasMore={hasMore} + hasMore={(!!nextTrendingStatuses) || hasMore} onLoadMore={handleLoadMore} placeholderComponent={placeholderComponent} placeholderCount={20} diff --git a/src/reducers/trending-statuses.ts b/src/reducers/trending-statuses.ts index e6f5bb72e..a86d66c11 100644 --- a/src/reducers/trending-statuses.ts +++ b/src/reducers/trending-statuses.ts @@ -3,6 +3,7 @@ import { OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord } from 'im import { TRENDING_STATUSES_FETCH_REQUEST, TRENDING_STATUSES_FETCH_SUCCESS, + TRENDING_STATUSES_EXPAND_SUCCESS, } from 'soapbox/actions/trending-statuses'; import type { AnyAction } from 'redux'; @@ -11,6 +12,7 @@ import type { APIEntity } from 'soapbox/types/entities'; const ReducerRecord = ImmutableRecord({ items: ImmutableOrderedSet(), isLoading: false, + next: null as string | null, }); type State = ReturnType; @@ -18,10 +20,11 @@ type APIEntities = Array; const toIds = (items: APIEntities) => ImmutableOrderedSet(items.map(item => item.id)); -const importStatuses = (state: State, statuses: APIEntities) => { +const importStatuses = (state: State, statuses: APIEntities, next: string|null) => { return state.withMutations(state => { - state.set('items', toIds(statuses)); + state.update('items', list => list.concat(toIds(statuses))); state.set('isLoading', false); + state.set('next', next ? next : null); }); }; @@ -29,8 +32,9 @@ export default function trending_statuses(state: State = ReducerRecord(), action switch (action.type) { case TRENDING_STATUSES_FETCH_REQUEST: return state.set('isLoading', true); + case TRENDING_STATUSES_EXPAND_SUCCESS: case TRENDING_STATUSES_FETCH_SUCCESS: - return importStatuses(state, action.statuses); + return importStatuses(state, action.statuses, action.next); default: return state; }