mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02: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:
		| @@ -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' }) | ||||||
|         return { |         res = await client.get( | ||||||
|           toots: await client.get( |  | ||||||
|           `${instanceLocal}timelines/public`, |           `${instanceLocal}timelines/public`, | ||||||
|           query, |           query, | ||||||
|           header |           header | ||||||
|         ) |         ) | ||||||
|  |         return { | ||||||
|  |           toots: res.body, | ||||||
|  |           pagination: getPagination(res.headers) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|       case 'LocalPublic': |       case 'LocalPublic': | ||||||
|         return { |         res = await client.get( | ||||||
|           toots: await client.get( |  | ||||||
|           `${instanceLocal}timelines/public`, |           `${instanceLocal}timelines/public`, | ||||||
|           query, |           query, | ||||||
|           header |           header | ||||||
|         ) |         ) | ||||||
|  |         return { | ||||||
|  |           toots: res.body, | ||||||
|  |           pagination: getPagination(res.headers) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|       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': | ||||||
|         return { |         res = await client.get( | ||||||
|           toots: await client.get( |  | ||||||
|           `${instanceLocal}accounts/${account}/statuses`, |           `${instanceLocal}accounts/${account}/statuses`, | ||||||
|           query, |           query, | ||||||
|           header |           header | ||||||
|         ) |         ) | ||||||
|         } |  | ||||||
|       case 'Account_Media': |  | ||||||
|         return { |         return { | ||||||
|           toots: await client.get( |           toots: res.body | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       case 'Account_Media': | ||||||
|  |         res = await client.get( | ||||||
|           `${instanceLocal}accounts/${account}/statuses`, |           `${instanceLocal}accounts/${account}/statuses`, | ||||||
|           [{ key: 'only_media', value: 'true' }], |           [{ key: 'only_media', value: 'true' }], | ||||||
|           header |           header | ||||||
|         ) |         ) | ||||||
|         } |  | ||||||
|       case 'Hashtag': |  | ||||||
|         return { |         return { | ||||||
|           toots: await client.get( |           toots: res.body | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       case 'Hashtag': | ||||||
|  |         res = await client.get( | ||||||
|           `${instanceLocal}timelines/tag/${hashtag}`, |           `${instanceLocal}timelines/tag/${hashtag}`, | ||||||
|           query, |           query, | ||||||
|           header |           header | ||||||
|         ) |         ) | ||||||
|         } |  | ||||||
|       case 'List': |  | ||||||
|         return { |         return { | ||||||
|           toots: await client.get( |           toots: res.body, | ||||||
|  |           pagination: getPagination(res.headers) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |       case 'List': | ||||||
|  |         res = await client.get( | ||||||
|           `${instanceLocal}timelines/list/${list}`, |           `${instanceLocal}timelines/list/${list}`, | ||||||
|           query, |           query, | ||||||
|           header |           header | ||||||
|         ) |         ) | ||||||
|  |         return { | ||||||
|  |           toots: res.body, | ||||||
|  |           pagination: getPagination(res.headers) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|       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) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       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 |         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' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user