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 | ||||
|   try { | ||||
|     const response = await fetch(`https://${url}${queryString}`, config) | ||||
|     const response = await fetch(`${url}${queryString}`, config) | ||||
|     data = await response.json() | ||||
|     if (response.ok) { | ||||
|       return data | ||||
|       return { headers: response.headers, body: data } | ||||
|     } | ||||
|     throw new Error(response.statusText) | ||||
|   } catch (err) { | ||||
|   | ||||
| @@ -43,26 +43,11 @@ export default function Timeline ({ | ||||
|           {...(state.pointer && { initialScrollIndex: state.pointer })} | ||||
|           {...(!disableRefresh && { | ||||
|             onRefresh: () => | ||||
|               dispatch( | ||||
|                 fetch({ | ||||
|                   page, | ||||
|                   query: [{ key: 'since_id', value: state.toots[0].id }] | ||||
|                 }) | ||||
|               ), | ||||
|               dispatch(fetch({ page, paginationDirection: 'prev' })), | ||||
|             refreshing: state.status === 'loading', | ||||
|             onEndReached: () => { | ||||
|               if (!timelineReady) { | ||||
|                 dispatch( | ||||
|                   fetch({ | ||||
|                     page, | ||||
|                     query: [ | ||||
|                       { | ||||
|                         key: 'max_id', | ||||
|                         value: state.toots[state.toots.length - 1].id | ||||
|                       } | ||||
|                     ] | ||||
|                   }) | ||||
|                 ) | ||||
|                 dispatch(fetch({ page, paginationDirection: 'next' })) | ||||
|                 setTimelineReady(true) | ||||
|               } | ||||
|             }, | ||||
|   | ||||
| @@ -5,9 +5,9 @@ import { client } from 'src/api/client' | ||||
| export const fetch = createAsyncThunk( | ||||
|   'account/fetch', | ||||
|   async ({ id }, { getState }) => { | ||||
|     const instanceLocal = getState().instanceInfo.local + '/api/v1/' | ||||
|  | ||||
|     return await client.get(`${instanceLocal}accounts/${id}`) | ||||
|     const instanceLocal = `https://${getState().instanceInfo.local}/api/v1/` | ||||
|     const res = await client.get(`${instanceLocal}accounts/${id}`) | ||||
|     return res.body | ||||
|   } | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -11,11 +11,35 @@ import { client } from 'src/api/client' | ||||
| // Hashtag:       hastag | ||||
| // 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( | ||||
|   'timeline/fetch', | ||||
|   async ({ page, query = [], account, hashtag, list, toot }, { getState }) => { | ||||
|     const instanceLocal = getState().instanceInfo.local + '/api/v1/' | ||||
|     const instanceRemote = getState().instanceInfo.remote + '/api/v1/' | ||||
|   async ( | ||||
|     { page, paginationDirection, query = [], account, hashtag, list, toot }, | ||||
|     { getState } | ||||
|   ) => { | ||||
|     const instanceLocal = `https://${getState().instanceInfo.local}/api/v1/` | ||||
|     const instanceRemote = `https://${getState().instanceInfo.remote}/api/v1/` | ||||
|     // If header if needed for remote server | ||||
|     const header = { | ||||
|       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) { | ||||
|       case 'Following': | ||||
|         res = await client.get(`${instanceLocal}timelines/home`, query, header) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}timelines/home`, | ||||
|             query, | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body, | ||||
|           pagination: getPagination(res.headers) | ||||
|         } | ||||
|  | ||||
|       case 'Local': | ||||
|         query.push({ key: 'local', value: 'true' }) | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}timelines/public`, | ||||
|           query, | ||||
|           header | ||||
|         ) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}timelines/public`, | ||||
|             query, | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body, | ||||
|           pagination: getPagination(res.headers) | ||||
|         } | ||||
|  | ||||
|       case 'LocalPublic': | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}timelines/public`, | ||||
|           query, | ||||
|           header | ||||
|         ) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}timelines/public`, | ||||
|             query, | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body, | ||||
|           pagination: getPagination(res.headers) | ||||
|         } | ||||
|  | ||||
|       case 'RemotePublic': | ||||
|         res = await client.get(`${instanceRemote}timelines/public`, query) | ||||
|         return { | ||||
|           toots: await client.get(`${instanceRemote}timelines/public`, query) | ||||
|           toots: res.body, | ||||
|           pagination: getPagination(res.headers) | ||||
|         } | ||||
|  | ||||
|       case 'Notifications': | ||||
|         res = await client.get(`${instanceLocal}notifications`, query, header) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}notifications`, | ||||
|             query, | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body, | ||||
|           pagination: getPagination(res.headers) | ||||
|         } | ||||
|  | ||||
|       case 'Account_Default': | ||||
|         const toots = await client.get( | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}accounts/${account}/statuses`, | ||||
|           [{ key: 'pinned', value: 'true' }], | ||||
|           header | ||||
|         ) | ||||
|         toots.push( | ||||
|           ...(await client.get( | ||||
|             `${instanceLocal}accounts/${account}/statuses`, | ||||
|             [{ key: 'exclude_replies', value: 'true' }], | ||||
|             header | ||||
|           )) | ||||
|         const toots = res.body | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}accounts/${account}/statuses`, | ||||
|           [{ key: 'exclude_replies', value: 'true' }], | ||||
|           header | ||||
|         ) | ||||
|         toots.push(...res.body) | ||||
|         return { toots: toots } | ||||
|  | ||||
|       case 'Account_All': | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}accounts/${account}/statuses`, | ||||
|           query, | ||||
|           header | ||||
|         ) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}accounts/${account}/statuses`, | ||||
|             query, | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body | ||||
|         } | ||||
|  | ||||
|       case 'Account_Media': | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}accounts/${account}/statuses`, | ||||
|           [{ key: 'only_media', value: 'true' }], | ||||
|           header | ||||
|         ) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}accounts/${account}/statuses`, | ||||
|             [{ key: 'only_media', value: 'true' }], | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body | ||||
|         } | ||||
|  | ||||
|       case 'Hashtag': | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}timelines/tag/${hashtag}`, | ||||
|           query, | ||||
|           header | ||||
|         ) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}timelines/tag/${hashtag}`, | ||||
|             query, | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body, | ||||
|           pagination: getPagination(res.headers) | ||||
|         } | ||||
|  | ||||
|       case 'List': | ||||
|         res = await client.get( | ||||
|           `${instanceLocal}timelines/list/${list}`, | ||||
|           query, | ||||
|           header | ||||
|         ) | ||||
|         return { | ||||
|           toots: await client.get( | ||||
|             `${instanceLocal}timelines/list/${list}`, | ||||
|             query, | ||||
|             header | ||||
|           ) | ||||
|           toots: res.body, | ||||
|           pagination: getPagination(res.headers) | ||||
|         } | ||||
|  | ||||
|       case 'Toot': | ||||
|         const current = await client.get( | ||||
|           `${instanceLocal}statuses/${toot}`, | ||||
| @@ -122,14 +179,16 @@ export const fetch = createAsyncThunk( | ||||
|           toots: [...context.ancestors, current, ...context.descendants], | ||||
|           pointer: context.ancestors.length | ||||
|         } | ||||
|  | ||||
|       default: | ||||
|         console.error('Timeline type error') | ||||
|         console.error('First time fetching timeline error') | ||||
|     } | ||||
|   } | ||||
| ) | ||||
|  | ||||
| const timelineInitState = { | ||||
|   toots: [], | ||||
|   pagination: { prev: undefined, next: undefined }, | ||||
|   pointer: undefined, | ||||
|   status: 'idle' | ||||
| } | ||||
| @@ -160,15 +219,22 @@ export const timelineSlice = createSlice({ | ||||
|     }, | ||||
|     [fetch.fulfilled]: (state, action) => { | ||||
|       state[action.meta.arg.page].status = 'succeeded' | ||||
|       if ( | ||||
|         action.meta.arg.query && | ||||
|         action.meta.arg.query[0].key === 'since_id' | ||||
|       ) { | ||||
|  | ||||
|       if (action.meta.arg.paginationDirection === 'prev') { | ||||
|         state[action.meta.arg.page].toots.unshift(...action.payload.toots) | ||||
|       } else { | ||||
|         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) => { | ||||
|       state[action.meta.arg.page].status = 'failed' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user