mirror of
https://github.com/tooot-app/app
synced 2025-02-07 05:45:23 +01:00
Rewrite timeline pagination
Using Link in returned headers instead. Also, `since_id` is not the correct way to refresh. See https://mastodonpy.readthedocs.io/en/stable/index.html?highlight=pagination#a-note-about-pagination
This commit is contained in:
parent
9c30edcc65
commit
2ab227a370
@ -23,10 +23,10 @@ export async function client (url, query, { body, ...customConfig } = {}) {
|
|||||||
|
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://${url}${queryString}`, config)
|
const response = await fetch(`${url}${queryString}`, config)
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return data
|
return { headers: response.headers, body: data }
|
||||||
}
|
}
|
||||||
throw new Error(response.statusText)
|
throw new Error(response.statusText)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -43,26 +43,11 @@ export default function Timeline ({
|
|||||||
{...(state.pointer && { initialScrollIndex: state.pointer })}
|
{...(state.pointer && { initialScrollIndex: state.pointer })}
|
||||||
{...(!disableRefresh && {
|
{...(!disableRefresh && {
|
||||||
onRefresh: () =>
|
onRefresh: () =>
|
||||||
dispatch(
|
dispatch(fetch({ page, paginationDirection: 'prev' })),
|
||||||
fetch({
|
|
||||||
page,
|
|
||||||
query: [{ key: 'since_id', value: state.toots[0].id }]
|
|
||||||
})
|
|
||||||
),
|
|
||||||
refreshing: state.status === 'loading',
|
refreshing: state.status === 'loading',
|
||||||
onEndReached: () => {
|
onEndReached: () => {
|
||||||
if (!timelineReady) {
|
if (!timelineReady) {
|
||||||
dispatch(
|
dispatch(fetch({ page, paginationDirection: 'next' }))
|
||||||
fetch({
|
|
||||||
page,
|
|
||||||
query: [
|
|
||||||
{
|
|
||||||
key: 'max_id',
|
|
||||||
value: state.toots[state.toots.length - 1].id
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
setTimelineReady(true)
|
setTimelineReady(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,9 +5,9 @@ import { client } from 'src/api/client'
|
|||||||
export const fetch = createAsyncThunk(
|
export const fetch = createAsyncThunk(
|
||||||
'account/fetch',
|
'account/fetch',
|
||||||
async ({ id }, { getState }) => {
|
async ({ id }, { getState }) => {
|
||||||
const instanceLocal = getState().instanceInfo.local + '/api/v1/'
|
const instanceLocal = `https://${getState().instanceInfo.local}/api/v1/`
|
||||||
|
const res = await client.get(`${instanceLocal}accounts/${id}`)
|
||||||
return await client.get(`${instanceLocal}accounts/${id}`)
|
return res.body
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,11 +11,35 @@ import { client } from 'src/api/client'
|
|||||||
// Hashtag: hastag
|
// Hashtag: hastag
|
||||||
// List: list
|
// List: list
|
||||||
|
|
||||||
|
function getPagination (headers, direction) {
|
||||||
|
if (!headers) console.error('Missing pagination headers')
|
||||||
|
const paginationLinks = headers.get('Link')
|
||||||
|
if (paginationLinks) {
|
||||||
|
if (direction) {
|
||||||
|
return {
|
||||||
|
[direction]: paginationLinks.split(
|
||||||
|
new RegExp(`<([^>]+)>; rel="${direction}"`)
|
||||||
|
)[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
prev: paginationLinks.split(new RegExp(/<([^>]+)>; rel="prev"/))[1],
|
||||||
|
next: paginationLinks.split(new RegExp(/<([^>]+)>; rel="next"/))[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const fetch = createAsyncThunk(
|
export const fetch = createAsyncThunk(
|
||||||
'timeline/fetch',
|
'timeline/fetch',
|
||||||
async ({ page, query = [], account, hashtag, list, toot }, { getState }) => {
|
async (
|
||||||
const instanceLocal = getState().instanceInfo.local + '/api/v1/'
|
{ page, paginationDirection, query = [], account, hashtag, list, toot },
|
||||||
const instanceRemote = getState().instanceInfo.remote + '/api/v1/'
|
{ getState }
|
||||||
|
) => {
|
||||||
|
const instanceLocal = `https://${getState().instanceInfo.local}/api/v1/`
|
||||||
|
const instanceRemote = `https://${getState().instanceInfo.remote}/api/v1/`
|
||||||
// If header if needed for remote server
|
// If header if needed for remote server
|
||||||
const header = {
|
const header = {
|
||||||
headers: {
|
headers: {
|
||||||
@ -23,90 +47,123 @@ export const fetch = createAsyncThunk(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let res
|
||||||
|
// For same page, but only pagination
|
||||||
|
if (paginationDirection) {
|
||||||
|
res = await client.get(
|
||||||
|
getState().timelines[page].pagination[paginationDirection],
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
toots: res.body,
|
||||||
|
pagination: getPagination(res.headers, paginationDirection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each page's first query
|
||||||
switch (page) {
|
switch (page) {
|
||||||
case 'Following':
|
case 'Following':
|
||||||
|
res = await client.get(`${instanceLocal}timelines/home`, query, header)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body,
|
||||||
`${instanceLocal}timelines/home`,
|
pagination: getPagination(res.headers)
|
||||||
query,
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Local':
|
case 'Local':
|
||||||
query.push({ key: 'local', value: 'true' })
|
query.push({ key: 'local', value: 'true' })
|
||||||
|
res = await client.get(
|
||||||
|
`${instanceLocal}timelines/public`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body,
|
||||||
`${instanceLocal}timelines/public`,
|
pagination: getPagination(res.headers)
|
||||||
query,
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'LocalPublic':
|
case 'LocalPublic':
|
||||||
|
res = await client.get(
|
||||||
|
`${instanceLocal}timelines/public`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body,
|
||||||
`${instanceLocal}timelines/public`,
|
pagination: getPagination(res.headers)
|
||||||
query,
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'RemotePublic':
|
case 'RemotePublic':
|
||||||
|
res = await client.get(`${instanceRemote}timelines/public`, query)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(`${instanceRemote}timelines/public`, query)
|
toots: res.body,
|
||||||
|
pagination: getPagination(res.headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Notifications':
|
case 'Notifications':
|
||||||
|
res = await client.get(`${instanceLocal}notifications`, query, header)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body,
|
||||||
`${instanceLocal}notifications`,
|
pagination: getPagination(res.headers)
|
||||||
query,
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Account_Default':
|
case 'Account_Default':
|
||||||
const toots = await client.get(
|
res = await client.get(
|
||||||
`${instanceLocal}accounts/${account}/statuses`,
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
[{ key: 'pinned', value: 'true' }],
|
[{ key: 'pinned', value: 'true' }],
|
||||||
header
|
header
|
||||||
)
|
)
|
||||||
toots.push(
|
const toots = res.body
|
||||||
...(await client.get(
|
res = await client.get(
|
||||||
`${instanceLocal}accounts/${account}/statuses`,
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
[{ key: 'exclude_replies', value: 'true' }],
|
[{ key: 'exclude_replies', value: 'true' }],
|
||||||
header
|
header
|
||||||
))
|
|
||||||
)
|
)
|
||||||
|
toots.push(...res.body)
|
||||||
return { toots: toots }
|
return { toots: toots }
|
||||||
|
|
||||||
case 'Account_All':
|
case 'Account_All':
|
||||||
|
res = await client.get(
|
||||||
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body
|
||||||
`${instanceLocal}accounts/${account}/statuses`,
|
|
||||||
query,
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Account_Media':
|
case 'Account_Media':
|
||||||
|
res = await client.get(
|
||||||
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
|
[{ key: 'only_media', value: 'true' }],
|
||||||
|
header
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body
|
||||||
`${instanceLocal}accounts/${account}/statuses`,
|
|
||||||
[{ key: 'only_media', value: 'true' }],
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Hashtag':
|
case 'Hashtag':
|
||||||
|
res = await client.get(
|
||||||
|
`${instanceLocal}timelines/tag/${hashtag}`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body,
|
||||||
`${instanceLocal}timelines/tag/${hashtag}`,
|
pagination: getPagination(res.headers)
|
||||||
query,
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'List':
|
case 'List':
|
||||||
|
res = await client.get(
|
||||||
|
`${instanceLocal}timelines/list/${list}`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
toots: await client.get(
|
toots: res.body,
|
||||||
`${instanceLocal}timelines/list/${list}`,
|
pagination: getPagination(res.headers)
|
||||||
query,
|
|
||||||
header
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Toot':
|
case 'Toot':
|
||||||
const current = await client.get(
|
const current = await client.get(
|
||||||
`${instanceLocal}statuses/${toot}`,
|
`${instanceLocal}statuses/${toot}`,
|
||||||
@ -122,14 +179,16 @@ export const fetch = createAsyncThunk(
|
|||||||
toots: [...context.ancestors, current, ...context.descendants],
|
toots: [...context.ancestors, current, ...context.descendants],
|
||||||
pointer: context.ancestors.length
|
pointer: context.ancestors.length
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error('Timeline type error')
|
console.error('First time fetching timeline error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const timelineInitState = {
|
const timelineInitState = {
|
||||||
toots: [],
|
toots: [],
|
||||||
|
pagination: { prev: undefined, next: undefined },
|
||||||
pointer: undefined,
|
pointer: undefined,
|
||||||
status: 'idle'
|
status: 'idle'
|
||||||
}
|
}
|
||||||
@ -160,15 +219,22 @@ export const timelineSlice = createSlice({
|
|||||||
},
|
},
|
||||||
[fetch.fulfilled]: (state, action) => {
|
[fetch.fulfilled]: (state, action) => {
|
||||||
state[action.meta.arg.page].status = 'succeeded'
|
state[action.meta.arg.page].status = 'succeeded'
|
||||||
if (
|
|
||||||
action.meta.arg.query &&
|
if (action.meta.arg.paginationDirection === 'prev') {
|
||||||
action.meta.arg.query[0].key === 'since_id'
|
|
||||||
) {
|
|
||||||
state[action.meta.arg.page].toots.unshift(...action.payload.toots)
|
state[action.meta.arg.page].toots.unshift(...action.payload.toots)
|
||||||
} else {
|
} else {
|
||||||
state[action.meta.arg.page].toots.push(...action.payload.toots)
|
state[action.meta.arg.page].toots.push(...action.payload.toots)
|
||||||
}
|
}
|
||||||
state[action.meta.arg.page].pointer = action.payload.pointer
|
|
||||||
|
if (action.payload.pagination) {
|
||||||
|
state[action.meta.arg.page].pagination = {
|
||||||
|
...state[action.meta.arg.page].pagination,
|
||||||
|
...action.payload.pagination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (action.payload.pointer) {
|
||||||
|
state[action.meta.arg.page].pointer = action.payload.pointer
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[fetch.rejected]: (state, action) => {
|
[fetch.rejected]: (state, action) => {
|
||||||
state[action.meta.arg.page].status = 'failed'
|
state[action.meta.arg.page].status = 'failed'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user