diff --git a/src/components/Timeline/Footer.tsx b/src/components/Timeline/Footer.tsx index c37fc27b..391c498f 100644 --- a/src/components/Timeline/Footer.tsx +++ b/src/components/Timeline/Footer.tsx @@ -16,16 +16,7 @@ export interface Props { const TimelineFooter: React.FC = ({ queryKey, disableInfinity }) => { const { hasNextPage } = useTimelineQuery({ ...queryKey[1], - options: { - enabled: !disableInfinity, - notifyOnChangeProps: ['hasNextPage'], - getNextPageParam: lastPage => - lastPage?.links?.next && { - ...(lastPage.links.next.isOffset - ? { offset: lastPage.links.next.id } - : { max_id: lastPage.links.next.id }) - } - } + options: { enabled: !disableInfinity, notifyOnChangeProps: ['hasNextPage'] } }) const { colors } = useTheme() diff --git a/src/components/Timeline/Refresh.tsx b/src/components/Timeline/Refresh.tsx index 6d9d31b7..c08a7a36 100644 --- a/src/components/Timeline/Refresh.tsx +++ b/src/components/Timeline/Refresh.tsx @@ -148,11 +148,7 @@ const TimelineRefresh: React.FC = ({ await queryFunctionTimeline({ queryKey, - pageParam: firstPage?.links?.prev && { - ...(firstPage.links.prev.isOffset - ? { offset: firstPage.links.prev.id } - : { min_id: firstPage.links.prev.id }) - }, + pageParam: firstPage?.links?.prev, meta: {} }) .then(res => { diff --git a/src/components/Timeline/index.tsx b/src/components/Timeline/index.tsx index 2f1ea0e7..0855292c 100644 --- a/src/components/Timeline/index.tsx +++ b/src/components/Timeline/index.tsx @@ -52,13 +52,7 @@ const Timeline: React.FC = ({ notifyOnChangeProps: Platform.select({ ios: ['dataUpdatedAt', 'isFetching'], android: ['dataUpdatedAt', 'isFetching', 'isLoading'] - }), - getNextPageParam: lastPage => - lastPage?.links?.next && { - ...(lastPage.links.next.isOffset - ? { offset: lastPage.links.next.id } - : { max_id: lastPage.links.next.id }) - } + }) } }) diff --git a/src/screens/Tabs/Me/List/Accounts.tsx b/src/screens/Tabs/Me/List/Accounts.tsx index 0ee1d0ef..a49624d4 100644 --- a/src/screens/Tabs/Me/List/Accounts.tsx +++ b/src/screens/Tabs/Me/List/Accounts.tsx @@ -23,17 +23,7 @@ const TabMeListAccounts: React.FC> const { t } = useTranslation(['common', 'screenTabs']) const queryKey: QueryKeyListAccounts = ['ListAccounts', { id: params.id }] - const { data, refetch, fetchNextPage, hasNextPage } = useListAccountsQuery({ - ...queryKey[1], - options: { - getNextPageParam: lastPage => - lastPage?.links?.next && { - ...(lastPage.links.next.isOffset - ? { offset: lastPage.links.next.id } - : { max_id: lastPage.links.next.id }) - } - } - }) + const { data, refetch, fetchNextPage, hasNextPage } = useListAccountsQuery({ ...queryKey[1] }) const mutation = useListAccountsMutation({ onSuccess: () => { diff --git a/src/screens/Tabs/Shared/Users.tsx b/src/screens/Tabs/Shared/Users.tsx index 7f6a118e..e161b1f8 100644 --- a/src/screens/Tabs/Shared/Users.tsx +++ b/src/screens/Tabs/Shared/Users.tsx @@ -33,12 +33,7 @@ const TabSharedUsers: React.FC> = const queryKey: QueryKeyUsers = ['Users', params] const { data, isFetching, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({ - ...queryKey[1], - options: { - getPreviousPageParam: firstPage => - firstPage.links?.prev?.id && { min_id: firstPage.links.prev.id }, - getNextPageParam: lastPage => lastPage.links?.next?.id && { max_id: lastPage.links.next.id } - } + ...queryKey[1] }) const [isSearching, setIsSearching] = useState(false) diff --git a/src/utils/api/general.ts b/src/utils/api/general.ts index d8433546..f2c929d6 100644 --- a/src/utils/api/general.ts +++ b/src/utils/api/general.ts @@ -1,5 +1,5 @@ import axios from 'axios' -import { ctx, handleError, PagedResponse, userAgent } from './helpers' +import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers' export type Params = { method: 'get' | 'post' | 'put' | 'delete' @@ -49,29 +49,7 @@ const apiGeneral = async ({ ? (body as (FormData & { _parts: [][] }) | undefined)?._parts?.length : Object.keys(body).length) && { data: body }) }) - .then(response => { - let links: { - prev?: { id: string; isOffset: boolean } - next?: { id: string; isOffset: boolean } - } = {} - - if (response.headers?.link) { - const linksParsed = response.headers.link.matchAll( - new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi') - ) - for (const link of linksParsed) { - switch (link[3]) { - case 'prev': - links.prev = { id: link[2], isOffset: link[1].includes('offset') } - break - case 'next': - links.next = { id: link[2], isOffset: link[1].includes('offset') } - break - } - } - } - return Promise.resolve({ body: response.data, links }) - }) + .then(response => ({ body: response.data, links: parseHeaderLinks(response.headers.link) })) .catch(handleError()) } diff --git a/src/utils/api/helpers/index.ts b/src/utils/api/helpers/index.ts index babf3ea5..bb160942 100644 --- a/src/utils/api/helpers/index.ts +++ b/src/utils/api/helpers/index.ts @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/react-native' import chalk from 'chalk' import Constants from 'expo-constants' import { Platform } from 'react-native' +import parse from 'url-parse' const userAgent = { 'User-Agent': `tooot/${Constants.expoConfig?.version} ${Platform.OS}/${Platform.Version}` @@ -64,10 +65,42 @@ const handleError = } } +export const parseHeaderLinks = (headerLink?: string): PagedResponse['links'] => { + if (!headerLink) return undefined + + const links: PagedResponse['links'] = {} + + const linkParsed = [...headerLink.matchAll(/<(\S+?)>; *rel="(next|prev)"/gi)] + for (const link of linkParsed) { + const queries = parse(link[1], true).query + const isOffset = !!queries.offset?.length + + switch (link[2]) { + case 'prev': + const prevId = isOffset ? queries.offset : queries.min_id + if (prevId) links.prev = isOffset ? { offset: prevId } : { min_id: prevId } + break + case 'next': + const nextId = isOffset ? queries.offset : queries.max_id + if (nextId) links.next = isOffset ? { offset: nextId } : { max_id: nextId } + break + } + } + + if (links.prev || links.next) { + return links + } else { + return undefined + } +} + type LinkFormat = { id: string; isOffset: boolean } export type PagedResponse = { body: T - links?: { prev?: LinkFormat; next?: LinkFormat } + links?: { + prev?: { min_id: string } | { offset: string } + next?: { max_id: string } | { offset: string } + } } export { ctx, handleError, userAgent } diff --git a/src/utils/api/instance.ts b/src/utils/api/instance.ts index 810df489..b31977dd 100644 --- a/src/utils/api/instance.ts +++ b/src/utils/api/instance.ts @@ -1,6 +1,6 @@ import { getAccountDetails } from '@utils/storage/actions' import axios, { AxiosRequestConfig } from 'axios' -import { ctx, handleError, PagedResponse, userAgent } from './helpers' +import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers' export type Params = { method: 'get' | 'post' | 'put' | 'delete' | 'patch' @@ -57,29 +57,7 @@ const apiInstance = async ({ ...((body as (FormData & { _parts: [][] }) | undefined)?._parts.length && { data: body }), ...extras }) - .then(response => { - let links: { - prev?: { id: string; isOffset: boolean } - next?: { id: string; isOffset: boolean } - } = {} - - if (response.headers?.link) { - const linksParsed = response.headers.link.matchAll( - new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi') - ) - for (const link of linksParsed) { - switch (link[3]) { - case 'prev': - links.prev = { id: link[2], isOffset: link[1].includes('offset') } - break - case 'next': - links.next = { id: link[2], isOffset: link[1].includes('offset') } - break - } - } - } - return Promise.resolve({ body: response.data, links }) - }) + .then(response => ({ body: response.data, links: parseHeaderLinks(response.headers.link) })) .catch(handleError()) } diff --git a/src/utils/queryHooks/lists.ts b/src/utils/queryHooks/lists.ts index a78c46a7..c38da5e4 100644 --- a/src/utils/queryHooks/lists.ts +++ b/src/utils/queryHooks/lists.ts @@ -1,15 +1,16 @@ import { - QueryFunctionContext, - useInfiniteQuery, - UseInfiniteQueryOptions, - useMutation, - UseMutationOptions, - useQuery, - UseQueryOptions + QueryFunctionContext, + useInfiniteQuery, + UseInfiniteQueryOptions, + useMutation, + UseMutationOptions, + useQuery, + UseQueryOptions } from '@tanstack/react-query' import { PagedResponse } from '@utils/api/helpers' import apiInstance from '@utils/api/instance' import { AxiosError } from 'axios' +import { infinitePageParams } from './utils' export type QueryKeyLists = ['Lists'] @@ -98,10 +99,16 @@ const useListAccountsQuery = ({ options, ...queryKeyParams }: QueryKeyListAccounts[1] & { - options?: UseInfiniteQueryOptions, AxiosError> + options?: Omit< + UseInfiniteQueryOptions, AxiosError>, + 'getPreviousPageParam' | 'getNextPageParam' + > }) => { const queryKey: QueryKeyListAccounts = ['ListAccounts', queryKeyParams] - return useInfiniteQuery(queryKey, accountsQueryFunction, options) + return useInfiniteQuery(queryKey, accountsQueryFunction, { + ...options, + ...infinitePageParams + }) } type AccountsMutationVarsLists = { diff --git a/src/utils/queryHooks/timeline.ts b/src/utils/queryHooks/timeline.ts index 5d5608a9..315cadf0 100644 --- a/src/utils/queryHooks/timeline.ts +++ b/src/utils/queryHooks/timeline.ts @@ -11,14 +11,14 @@ import apiInstance from '@utils/api/instance' import { featureCheck } from '@utils/helpers/featureCheck' import { useNavState } from '@utils/navigation/navigators' import { queryClient } from '@utils/queryHooks' -import { StorageAccount } from '@utils/storage/account' -import { getAccountStorage, setAccountStorage } from '@utils/storage/actions' +import { getAccountStorage } from '@utils/storage/actions' import { AxiosError } from 'axios' import { uniqBy } from 'lodash' import { searchLocalStatus } from './search' import deleteItem from './timeline/deleteItem' import editItem from './timeline/editItem' import updateStatusProperty from './timeline/updateStatusProperty' +import { infinitePageParams } from './utils' export type QueryKeyTimeline = [ 'Timeline', @@ -156,7 +156,16 @@ export const queryFunctionTimeline = async ({ case 'Account': if (!page.id) return Promise.reject('Timeline query account id not provided') - if (page.exclude_reblogs) { + if (page.only_media) { + return apiInstance({ + method: 'get', + url: `accounts/${page.id}/statuses`, + params: { + only_media: 'true', + ...params + } + }) + } else if (page.exclude_reblogs) { if (pageParam && pageParam.hasOwnProperty('max_id')) { return apiInstance({ method: 'get', @@ -196,8 +205,8 @@ export const queryFunctionTimeline = async ({ url: `accounts/${page.id}/statuses`, params: { ...params, - exclude_replies: page.exclude_reblogs.toString(), - only_media: page.only_media.toString() + exclude_replies: false, + only_media: false } }) } @@ -247,14 +256,18 @@ const useTimelineQuery = ({ options, ...queryKeyParams }: QueryKeyTimeline[1] & { - options?: UseInfiniteQueryOptions, AxiosError> + options?: Omit< + UseInfiniteQueryOptions, AxiosError>, + 'getPreviousPageParam' | 'getNextPageParam' + > }) => { const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }] return useInfiniteQuery(queryKey, queryFunctionTimeline, { refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - ...options + ...options, + ...infinitePageParams }) } diff --git a/src/utils/queryHooks/users.ts b/src/utils/queryHooks/users.ts index 14c78345..87fd1366 100644 --- a/src/utils/queryHooks/users.ts +++ b/src/utils/queryHooks/users.ts @@ -9,6 +9,7 @@ import apiInstance from '@utils/api/instance' import { urlMatcher } from '@utils/helpers/urlMatcher' import { TabSharedStackParamList } from '@utils/navigation/navigators' import { AxiosError } from 'axios' +import { infinitePageParams } from './utils' export type QueryKeyUsers = ['Users', TabSharedStackParamList['Tab-Shared-Users']] @@ -73,13 +74,19 @@ const useUsersQuery = ({ options, ...queryKeyParams }: QueryKeyUsers[1] & { - options?: UseInfiniteQueryOptions< - PagedResponse & { warnIncomplete: boolean; remoteData: boolean }, - AxiosError + options?: Omit< + UseInfiniteQueryOptions< + PagedResponse & { warnIncomplete: boolean; remoteData: boolean }, + AxiosError + >, + 'getPreviousPageParam' | 'getNextPageParam' > }) => { const queryKey: QueryKeyUsers = ['Users', { ...queryKeyParams }] - return useInfiniteQuery(queryKey, queryFunction, options) + return useInfiniteQuery(queryKey, queryFunction, { + ...options, + ...infinitePageParams + }) } export { useUsersQuery } diff --git a/src/utils/queryHooks/utils.ts b/src/utils/queryHooks/utils.ts index ad06ff9e..e83c3a29 100644 --- a/src/utils/queryHooks/utils.ts +++ b/src/utils/queryHooks/utils.ts @@ -2,10 +2,8 @@ import { InfiniteData } from '@tanstack/react-query' import { PagedResponse } from '@utils/api/helpers' export const infinitePageParams = { - getPreviousPageParam: (firstPage: PagedResponse) => - firstPage.links?.prev && { min_id: firstPage.links.next }, - getNextPageParam: (lastPage: PagedResponse) => - lastPage.links?.next && { max_id: lastPage.links.next } + getPreviousPageParam: (firstPage: PagedResponse) => firstPage.links?.prev, + getNextPageParam: (lastPage: PagedResponse) => lastPage.links?.next } export const flattenPages = (data: InfiniteData> | undefined): T[] | [] =>