Merge branch 'next-paginated-context' into 'next'
Next: Support paginated context API See merge request soapbox-pub/soapbox-fe!1253
This commit is contained in:
commit
3b55a5a9c7
|
@ -2,7 +2,7 @@ import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
import { getFeatures, parseVersion } from 'soapbox/utils/features';
|
import { getFeatures, parseVersion } from 'soapbox/utils/features';
|
||||||
import { shouldHaveCard } from 'soapbox/utils/status';
|
import { shouldHaveCard } from 'soapbox/utils/status';
|
||||||
|
|
||||||
import api from '../api';
|
import api, { getNextLink } from '../api';
|
||||||
|
|
||||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||||
import { openModal } from './modals';
|
import { openModal } from './modals';
|
||||||
|
@ -167,12 +167,49 @@ export function fetchContext(id) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchNext(next) {
|
||||||
|
return async(dispatch, getState) => {
|
||||||
|
const response = await api(getState).get(next);
|
||||||
|
dispatch(importFetchedStatuses(response.data));
|
||||||
|
return { next: getNextLink(response) };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchAncestors(id) {
|
||||||
|
return async(dispatch, getState) => {
|
||||||
|
const response = await api(getState).get(`/api/v1/statuses/${id}/context/ancestors`);
|
||||||
|
dispatch(importFetchedStatuses(response.data));
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchDescendants(id) {
|
||||||
|
return async(dispatch, getState) => {
|
||||||
|
const response = await api(getState).get(`/api/v1/statuses/${id}/context/descendants`);
|
||||||
|
dispatch(importFetchedStatuses(response.data));
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchStatusWithContext(id) {
|
export function fetchStatusWithContext(id) {
|
||||||
return (dispatch, getState) => {
|
return async(dispatch, getState) => {
|
||||||
return Promise.all([
|
const features = getFeatures(getState().instance);
|
||||||
|
|
||||||
|
if (features.paginatedContext) {
|
||||||
|
const responses = await Promise.all([
|
||||||
|
dispatch(fetchAncestors(id)),
|
||||||
|
dispatch(fetchDescendants(id)),
|
||||||
|
dispatch(fetchStatus(id)),
|
||||||
|
]);
|
||||||
|
const next = getNextLink(responses[1]);
|
||||||
|
return { next };
|
||||||
|
} else {
|
||||||
|
await Promise.all([
|
||||||
dispatch(fetchContext(id)),
|
dispatch(fetchContext(id)),
|
||||||
dispatch(fetchStatus(id)),
|
dispatch(fetchStatus(id)),
|
||||||
]);
|
]);
|
||||||
|
return { next: undefined };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@ export const getLinks = (response: AxiosResponse): LinkHeader => {
|
||||||
return new LinkHeader(response.headers?.link);
|
return new LinkHeader(response.headers?.link);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getNextLink = (response: AxiosResponse): string | undefined => {
|
||||||
|
return getLinks(response).refs.find(link => link.rel === 'next')?.uri;
|
||||||
|
};
|
||||||
|
|
||||||
const getToken = (state: RootState, authType: string) => {
|
const getToken = (state: RootState, authType: string) => {
|
||||||
return authType === 'app' ? getAppToken(state) : getAccessToken(state);
|
return authType === 'app' ? getAppToken(state) : getAccessToken(state);
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,7 +51,7 @@ import {
|
||||||
hideStatus,
|
hideStatus,
|
||||||
revealStatus,
|
revealStatus,
|
||||||
} from '../../actions/statuses';
|
} from '../../actions/statuses';
|
||||||
import { fetchStatusWithContext } from '../../actions/statuses';
|
import { fetchStatusWithContext, fetchNext } from '../../actions/statuses';
|
||||||
import MissingIndicator from '../../components/missing_indicator';
|
import MissingIndicator from '../../components/missing_indicator';
|
||||||
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
||||||
import { makeGetStatus } from '../../selectors';
|
import { makeGetStatus } from '../../selectors';
|
||||||
|
@ -189,6 +189,7 @@ interface IStatusState {
|
||||||
emojiSelectorFocused: boolean,
|
emojiSelectorFocused: boolean,
|
||||||
isLoaded: boolean,
|
isLoaded: boolean,
|
||||||
error?: AxiosError,
|
error?: AxiosError,
|
||||||
|
next?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
|
@ -200,17 +201,18 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
emojiSelectorFocused: false,
|
emojiSelectorFocused: false,
|
||||||
isLoaded: Boolean(this.props.status),
|
isLoaded: Boolean(this.props.status),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
next: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
node: HTMLDivElement | null = null;
|
node: HTMLDivElement | null = null;
|
||||||
status: HTMLDivElement | null = null;
|
status: HTMLDivElement | null = null;
|
||||||
_scrolledIntoView: boolean = false;
|
_scrolledIntoView: boolean = false;
|
||||||
|
|
||||||
fetchData = () => {
|
fetchData = async() => {
|
||||||
const { dispatch, params } = this.props;
|
const { dispatch, params } = this.props;
|
||||||
const { statusId } = params;
|
const { statusId } = params;
|
||||||
|
const { next } = await dispatch(fetchStatusWithContext(statusId));
|
||||||
return dispatch(fetchStatusWithContext(statusId));
|
this.setState({ next });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -641,6 +643,16 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
return this.fetchData();
|
return this.fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLoadMore = () => {
|
||||||
|
const { next } = this.state;
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
this.props.dispatch(fetchNext(next)).then(({ next }) => {
|
||||||
|
this.setState({ next });
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { status, ancestorsIds, descendantsIds, intl } = this.props;
|
const { status, ancestorsIds, descendantsIds, intl } = this.props;
|
||||||
|
|
||||||
|
@ -754,7 +766,12 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref={this.setRef} className='thread'>
|
<div ref={this.setRef} className='thread'>
|
||||||
<ScrollableList onRefresh={this.handleRefresh}>
|
<ScrollableList
|
||||||
|
onRefresh={this.handleRefresh}
|
||||||
|
hasMore={!!this.state.next}
|
||||||
|
onLoadMore={this.handleLoadMore}
|
||||||
|
placeholderComponent={() => <PlaceholderStatus thread />}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -286,6 +286,13 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
v.software === PLEROMA && gte(v.version, '2.4.50'),
|
v.software === PLEROMA && gte(v.version, '2.4.50'),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports pagination in threads.
|
||||||
|
* @see GET /api/v1/statuses/:id/context/ancestors
|
||||||
|
* @see GET /api/v1/statuses/:id/context/descendants
|
||||||
|
*/
|
||||||
|
paginatedContext: v.software === TRUTHSOCIAL,
|
||||||
|
|
||||||
/** Truth Social account registration API. */
|
/** Truth Social account registration API. */
|
||||||
pepe: v.software === TRUTHSOCIAL,
|
pepe: v.software === TRUTHSOCIAL,
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue