feat: create pagination with Link Headers for trending statuses

This commit is contained in:
P. Reis 2024-10-18 19:32:56 -03:00
parent 93a464d210
commit 4cda1e2866
3 changed files with 68 additions and 10 deletions

View File

@ -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,
};

View File

@ -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}

View File

@ -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<string>(),
isLoading: false,
next: null as string | null,
});
type State = ReturnType<typeof ReducerRecord>;
@ -18,10 +20,11 @@ type APIEntities = Array<APIEntity>;
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;
}