forked from Ivasoft/mattermost-mobile
[Gekidou MM-42242 MM-46043] Paginating Threads (#6535)
* Pagination and bug * Feedback and version update * Making the args optional * Code refactor
This commit is contained in:
committed by
GitHub
parent
e443a69265
commit
690dd1e66e
@@ -324,7 +324,7 @@ export async function updateThread(serverUrl: string, threadId: string, updatedT
|
||||
}
|
||||
return {model};
|
||||
} catch (error) {
|
||||
logError('Failed markTeamThreadsAsRead', error);
|
||||
logError('Failed updateThread', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,7 +570,7 @@ export const fetchPostAuthors = async (serverUrl: string, posts: Post[], fetchOn
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchPostThread(serverUrl: string, postId: string, fetchOnly = false) {
|
||||
export async function fetchPostThread(serverUrl: string, postId: string, options?: FetchPaginatedThreadOptions, fetchOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -585,7 +585,13 @@ export async function fetchPostThread(serverUrl: string, postId: string, fetchOn
|
||||
|
||||
try {
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
const data = await client.getPostThread(postId, isCRTEnabled, isCRTEnabled);
|
||||
|
||||
// Not doing any version check as server versions below 6.7 will ignore the additional params from the client.
|
||||
const data = await client.getPostThread(postId, {
|
||||
collapsedThreads: isCRTEnabled,
|
||||
collapsedThreadsExtended: isCRTEnabled,
|
||||
...options,
|
||||
});
|
||||
const result = processPostsFetched(data);
|
||||
let posts: Model[] = [];
|
||||
if (!fetchOnly) {
|
||||
@@ -637,7 +643,11 @@ export async function fetchPostsAround(serverUrl: string, channelId: string, pos
|
||||
try {
|
||||
const [after, post, before] = await Promise.all<PostsObjectsRequest>([
|
||||
client.getPostsAfter(channelId, postId, 0, perPage, isCRTEnabled, isCRTEnabled),
|
||||
client.getPostThread(postId, isCRTEnabled, isCRTEnabled),
|
||||
client.getPostThread(postId, {
|
||||
collapsedThreads: isCRTEnabled,
|
||||
collapsedThreadsExtended: isCRTEnabled,
|
||||
fetchAll: true,
|
||||
}),
|
||||
client.getPostsBefore(channelId, postId, 0, perPage, isCRTEnabled, isCRTEnabled),
|
||||
]);
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string,
|
||||
}
|
||||
|
||||
// Load thread before we open to the thread modal
|
||||
// @Todo: https://mattermost.atlassian.net/browse/MM-42232
|
||||
fetchPostThread(serverUrl, rootId);
|
||||
|
||||
// Mark thread as read
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface ClientPostsMix {
|
||||
getPost: (postId: string) => Promise<Post>;
|
||||
patchPost: (postPatch: Partial<Post> & {id: string}) => Promise<Post>;
|
||||
deletePost: (postId: string) => Promise<any>;
|
||||
getPostThread: (postId: string, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<any>;
|
||||
getPostThread: (postId: string, options: FetchPaginatedThreadOptions) => Promise<PostResponse>;
|
||||
getPosts: (channelId: string, page?: number, perPage?: number, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<PostResponse>;
|
||||
getPostsSince: (channelId: string, since: number, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<PostResponse>;
|
||||
getPostsBefore: (channelId: string, postId: string, page?: number, perPage?: number, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<PostResponse>;
|
||||
@@ -79,9 +79,18 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
);
|
||||
};
|
||||
|
||||
getPostThread = async (postId: string, collapsedThreads = false, collapsedThreadsExtended = false) => {
|
||||
getPostThread = (postId: string, options: FetchPaginatedThreadOptions): Promise<PostResponse> => {
|
||||
const {
|
||||
fetchThreads = true,
|
||||
collapsedThreads = false,
|
||||
collapsedThreadsExtended = false,
|
||||
direction = 'up',
|
||||
fetchAll = false,
|
||||
perPage = fetchAll ? undefined : PER_PAGE_DEFAULT,
|
||||
...rest
|
||||
} = options;
|
||||
return this.doFetch(
|
||||
`${this.getPostRoute(postId)}/thread${buildQueryString({collapsedThreads, collapsedThreadsExtended})}`,
|
||||
`${this.getPostRoute(postId)}/thread${buildQueryString({skipFetchThreads: !fetchThreads, collapsedThreads, collapsedThreadsExtended, direction, perPage, ...rest})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -149,10 +149,17 @@ const PostList = ({
|
||||
if (location === Screens.CHANNEL && channelId) {
|
||||
await fetchPosts(serverUrl, channelId);
|
||||
} else if (location === Screens.THREAD && rootId) {
|
||||
await fetchPostThread(serverUrl, rootId);
|
||||
const options: FetchPaginatedThreadOptions = {};
|
||||
const lastPost = posts[0];
|
||||
if (lastPost) {
|
||||
options.fromCreateAt = lastPost.createAt;
|
||||
options.fromPost = lastPost.id;
|
||||
options.direction = 'down';
|
||||
}
|
||||
await fetchPostThread(serverUrl, rootId, options);
|
||||
}
|
||||
setRefreshing(false);
|
||||
}, [channelId, location, rootId]);
|
||||
}, [channelId, location, posts, rootId]);
|
||||
|
||||
const onScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (Platform.OS === 'android') {
|
||||
|
||||
@@ -25,7 +25,7 @@ import {DEFAULT_LOCALE, getLocalizedMessage, t} from '@i18n';
|
||||
import NativeNotifications from '@notifications';
|
||||
import {queryServerName} from '@queries/app/servers';
|
||||
import {getCurrentChannelId} from '@queries/servers/system';
|
||||
import {getIsCRTEnabled} from '@queries/servers/thread';
|
||||
import {getIsCRTEnabled, getThreadById} from '@queries/servers/thread';
|
||||
import {showOverlay} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
@@ -139,7 +139,10 @@ class PushNotifications {
|
||||
if (database) {
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
if (isCRTEnabled && payload.root_id) {
|
||||
markThreadAsRead(serverUrl, payload.team_id, payload.post_id);
|
||||
const thread = await getThreadById(database, payload.root_id);
|
||||
if (thread?.isFollowing) {
|
||||
markThreadAsRead(serverUrl, payload.team_id, payload.post_id);
|
||||
}
|
||||
} else {
|
||||
markChannelAsViewed(serverUrl, payload.channel_id, false);
|
||||
}
|
||||
|
||||
@@ -155,7 +155,9 @@ function Permalink({
|
||||
let data;
|
||||
const loadThreadPosts = isCRTEnabled && rootId;
|
||||
if (loadThreadPosts) {
|
||||
data = await fetchPostThread(serverUrl, rootId);
|
||||
data = await fetchPostThread(serverUrl, rootId, {
|
||||
fetchAll: true,
|
||||
});
|
||||
} else {
|
||||
data = await fetchPostsAround(serverUrl, channelId, postId, POSTS_LIMIT, isCRTEnabled);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeMyChannel, observeChannel} from '@queries/servers/channel';
|
||||
import {queryPostsChunk, queryPostsInThread} from '@queries/servers/post';
|
||||
import {observeConfigValue} from '@queries/servers/system';
|
||||
import {observeIsCRTEnabled, observeThreadById} from '@queries/servers/thread';
|
||||
|
||||
import ThreadPostList from './thread_post_list';
|
||||
@@ -41,6 +42,7 @@ const enhanced = withObservables(['forceQueryAfterAppState', 'rootPost'], ({data
|
||||
switchMap((channel) => of$(channel?.teamId)),
|
||||
),
|
||||
thread: observeThreadById(database, rootPost.id),
|
||||
version: observeConfigValue(database, 'Version'),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useMemo, useRef} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {fetchPostThread} from '@actions/remote/post';
|
||||
import {markThreadAsRead} from '@actions/remote/thread';
|
||||
import PostList from '@components/post_list';
|
||||
import {Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {debounce} from '@helpers/api/general';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {isMinimumServerVersion} from '@utils/helpers';
|
||||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type ThreadModel from '@typings/database/models/servers/thread';
|
||||
@@ -22,6 +25,7 @@ type Props = {
|
||||
rootPost: PostModel;
|
||||
teamId: string;
|
||||
thread?: ThreadModel;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
const edges: Edge[] = ['bottom'];
|
||||
@@ -34,18 +38,35 @@ const styles = StyleSheet.create({
|
||||
|
||||
const ThreadPostList = ({
|
||||
channelLastViewedAt, isCRTEnabled,
|
||||
nativeID, posts, rootPost, teamId, thread,
|
||||
nativeID, posts, rootPost, teamId, thread, version,
|
||||
}: Props) => {
|
||||
const isTablet = useIsTablet();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const canLoadPosts = useRef(true);
|
||||
const fetchingPosts = useRef(false);
|
||||
const onEndReached = useCallback(debounce(async () => {
|
||||
if (isMinimumServerVersion(version || '', 6, 7) && !fetchingPosts.current && canLoadPosts.current && posts.length) {
|
||||
fetchingPosts.current = true;
|
||||
const options: FetchPaginatedThreadOptions = {};
|
||||
const lastPost = posts[posts.length - 1];
|
||||
if (lastPost) {
|
||||
options.fromPost = lastPost.id;
|
||||
options.fromCreateAt = lastPost.createAt;
|
||||
}
|
||||
const result = await fetchPostThread(serverUrl, rootPost.id, options);
|
||||
fetchingPosts.current = false;
|
||||
canLoadPosts.current = Boolean(result?.posts?.length);
|
||||
}
|
||||
}, 500), [rootPost, posts, version]);
|
||||
|
||||
const threadPosts = useMemo(() => {
|
||||
return [...posts, rootPost];
|
||||
}, [posts, rootPost]);
|
||||
|
||||
// If CRT is enabled, mark the thread as read on mount.
|
||||
useEffect(() => {
|
||||
if (isCRTEnabled) {
|
||||
if (isCRTEnabled && thread?.isFollowing) {
|
||||
markThreadAsRead(serverUrl, teamId, rootPost.id);
|
||||
}
|
||||
}, []);
|
||||
@@ -69,6 +90,7 @@ const ThreadPostList = ({
|
||||
lastViewedAt={lastViewedAt}
|
||||
location={Screens.THREAD}
|
||||
nativeID={nativeID}
|
||||
onEndReached={onEndReached}
|
||||
posts={threadPosts}
|
||||
rootId={rootPost.id}
|
||||
shouldShowJoinLeaveMessages={false}
|
||||
|
||||
11
types/api/posts.d.ts
vendored
11
types/api/posts.d.ts
vendored
@@ -116,3 +116,14 @@ type PostSearchParams = {
|
||||
terms: string;
|
||||
is_or_search: boolean;
|
||||
};
|
||||
|
||||
type FetchPaginatedThreadOptions = {
|
||||
fetchThreads?: boolean;
|
||||
collapsedThreads?: boolean;
|
||||
collapsedThreadsExtended?: boolean;
|
||||
direction?: 'up'|'down';
|
||||
fetchAll?: boolean;
|
||||
perPage?: number;
|
||||
fromCreateAt?: number;
|
||||
fromPost?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user