diff --git a/src/api/client.js b/src/api/client.js index 0e0d3723..0f9a34ac 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -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) { diff --git a/src/stacks/common/Timeline.jsx b/src/stacks/common/Timeline.jsx index e1461114..9ebbbfdb 100644 --- a/src/stacks/common/Timeline.jsx +++ b/src/stacks/common/Timeline.jsx @@ -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) } }, diff --git a/src/stacks/common/accountSlice.js b/src/stacks/common/accountSlice.js index 3cee6ab5..22b7b6ee 100644 --- a/src/stacks/common/accountSlice.js +++ b/src/stacks/common/accountSlice.js @@ -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 } ) diff --git a/src/stacks/common/timelineSlice.js b/src/stacks/common/timelineSlice.js index 2b3d2c25..409fcb0d 100644 --- a/src/stacks/common/timelineSlice.js +++ b/src/stacks/common/timelineSlice.js @@ -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'