Use Link header for home timeline pagination

This commit is contained in:
Alex Gleason 2023-04-02 18:32:50 -05:00
parent 9c3af7a0c9
commit 2bc6ff3fa3
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
5 changed files with 74 additions and 15 deletions

View file

@ -4,7 +4,7 @@ import { getSettings } from 'soapbox/actions/settings';
import { normalizeStatus } from 'soapbox/normalizers';
import { shouldFilter } from 'soapbox/utils/timelines';
import api, { getLinks } from '../api';
import api, { getNextLink, getPrevLink } from '../api';
import { importFetchedStatus, importFetchedStatuses } from './importer';
@ -139,7 +139,7 @@ const parseTags = (tags: Record<string, any[]> = {}, mode: 'any' | 'all' | 'none
};
const replaceHomeTimeline = (
accountId: string | null,
accountId: string | undefined,
{ maxId }: Record<string, any> = {},
done?: () => void,
) => (dispatch: AppDispatch, _getState: () => RootState) => {
@ -162,7 +162,12 @@ const expandTimeline = (timelineId: string, path: string, params: Record<string,
return dispatch(noOpAsync());
}
if (!params.max_id && !params.pinned && (timeline.items || ImmutableOrderedSet()).size > 0) {
if (
!params.max_id &&
!params.pinned &&
(timeline.items || ImmutableOrderedSet()).size > 0 &&
!path.includes('max_id=')
) {
params.since_id = timeline.getIn(['items', 0]);
}
@ -171,9 +176,16 @@ const expandTimeline = (timelineId: string, path: string, params: Record<string,
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
return api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore));
dispatch(expandTimelineSuccess(
timelineId,
response.data,
getNextLink(response),
getPrevLink(response),
response.status === 206,
isLoadingRecent,
isLoadingMore,
));
done();
}).catch(error => {
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
@ -181,9 +193,26 @@ const expandTimeline = (timelineId: string, path: string, params: Record<string,
});
};
const expandHomeTimeline = ({ accountId, maxId }: Record<string, any> = {}, done = noOp) => {
const endpoint = accountId ? `/api/v1/accounts/${accountId}/statuses` : '/api/v1/timelines/home';
const params: any = { max_id: maxId };
interface ExpandHomeTimelineOpts {
accountId?: string
maxId?: string
url?: string
}
interface HomeTimelineParams {
max_id?: string
exclude_replies?: boolean
with_muted?: boolean
}
const expandHomeTimeline = ({ url, accountId, maxId }: ExpandHomeTimelineOpts = {}, done = noOp) => {
const endpoint = url || (accountId ? `/api/v1/accounts/${accountId}/statuses` : '/api/v1/timelines/home');
const params: HomeTimelineParams = {};
if (!url && maxId) {
params.max_id = maxId;
}
if (accountId) {
params.exclude_replies = true;
params.with_muted = true;
@ -237,11 +266,20 @@ const expandTimelineRequest = (timeline: string, isLoadingMore: boolean) => ({
skipLoading: !isLoadingMore,
});
const expandTimelineSuccess = (timeline: string, statuses: APIEntity[], next: string | null, partial: boolean, isLoadingRecent: boolean, isLoadingMore: boolean) => ({
const expandTimelineSuccess = (
timeline: string,
statuses: APIEntity[],
next: string | undefined,
prev: string | undefined,
partial: boolean,
isLoadingRecent: boolean,
isLoadingMore: boolean,
) => ({
type: TIMELINE_EXPAND_SUCCESS,
timeline,
statuses,
next,
prev,
partial,
isLoadingRecent,
skipLoading: !isLoadingMore,

View file

@ -30,7 +30,7 @@ const CarouselItem = React.forwardRef((
setLoading(true);
if (isSelected) {
dispatch(replaceHomeTimeline(null, { maxId: null }, () => setLoading(false)));
dispatch(replaceHomeTimeline(undefined, { maxId: null }, () => setLoading(false)));
if (onPinned) {
onPinned(null);

View file

@ -27,9 +27,10 @@ const HomeTimeline: React.FC = () => {
const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true);
const currentAccountId = useAppSelector(state => state.timelines.get('home')?.feedAccountId as string | undefined);
const currentAccountRelationship = useAppSelector(state => currentAccountId ? state.relationships.get(currentAccountId) : null);
const next = useAppSelector(state => state.timelines.get('home')?.next);
const handleLoadMore = (maxId: string) => {
dispatch(expandHomeTimeline({ maxId, accountId: currentAccountId }));
dispatch(expandHomeTimeline({ url: next, maxId, accountId: currentAccountId }));
};
// Mastodon generates the feed in Redis, and can return a partial timeline
@ -52,7 +53,7 @@ const HomeTimeline: React.FC = () => {
};
const handleRefresh = () => {
return dispatch(expandHomeTimeline({ maxId: null, accountId: currentAccountId }));
return dispatch(expandHomeTimeline({ accountId: currentAccountId }));
};
useEffect(() => {

View file

@ -35,7 +35,7 @@ const TestTimeline: React.FC = () => {
React.useEffect(() => {
dispatch(importFetchedStatuses(MOCK_STATUSES));
dispatch(expandTimelineSuccess(timelineId, MOCK_STATUSES, null, false, false, false));
dispatch(expandTimelineSuccess(timelineId, MOCK_STATUSES, undefined, undefined, false, false, false));
}, []);
return (

View file

@ -46,6 +46,8 @@ const TimelineRecord = ImmutableRecord({
top: true,
isLoading: false,
hasMore: true,
next: undefined as string | undefined,
prev: undefined as string | undefined,
items: ImmutableOrderedSet<string>(),
queuedItems: ImmutableOrderedSet<string>(), //max= MAX_QUEUED_ITEMS
feedAccountId: null,
@ -87,13 +89,23 @@ const setFailed = (state: State, timelineId: string, failed: boolean) => {
return state.update(timelineId, TimelineRecord(), timeline => timeline.set('loadingFailed', failed));
};
const expandNormalizedTimeline = (state: State, timelineId: string, statuses: ImmutableList<ImmutableMap<string, any>>, next: string | null, isPartial: boolean, isLoadingRecent: boolean) => {
const expandNormalizedTimeline = (
state: State,
timelineId: string,
statuses: ImmutableList<ImmutableMap<string, any>>,
next: string | undefined,
prev: string | undefined,
isPartial: boolean,
isLoadingRecent: boolean,
) => {
const newIds = getStatusIds(statuses);
return state.update(timelineId, TimelineRecord(), timeline => timeline.withMutations(timeline => {
timeline.set('isLoading', false);
timeline.set('loadingFailed', false);
timeline.set('isPartial', isPartial);
timeline.set('next', next);
timeline.set('prev', prev);
if (!next && !isLoadingRecent) timeline.set('hasMore', false);
@ -322,7 +334,15 @@ export default function timelines(state: State = initialState, action: AnyAction
case TIMELINE_EXPAND_FAIL:
return handleExpandFail(state, action.timeline);
case TIMELINE_EXPAND_SUCCESS:
return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses) as ImmutableList<ImmutableMap<string, any>>, action.next, action.partial, action.isLoadingRecent);
return expandNormalizedTimeline(
state,
action.timeline,
fromJS(action.statuses) as ImmutableList<ImmutableMap<string, any>>,
action.next,
action.prev,
action.partial,
action.isLoadingRecent,
);
case TIMELINE_UPDATE:
return updateTimeline(state, action.timeline, action.statusId);
case TIMELINE_UPDATE_QUEUE: