forked from Ivasoft/mattermost-mobile
[Gekidou] Handle post related websocket events (#5851)
* Handle post related websocket events * Address feedback and several fixes * Fix lint * Address feedback * LastPostAt as latest post create_at, and lastViewedAt as date.now() * Address feedback * Change database for operator and add type imports
This commit is contained in:
committed by
GitHub
parent
d5228633b2
commit
1e355ee684
@@ -8,7 +8,7 @@ import {General, Navigation as NavigationConstants, Preferences, Screens} from '
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
|
||||
import {prepareDeleteChannel, queryAllMyChannelIds, queryChannelsById, queryMyChannel} from '@queries/servers/channel';
|
||||
import {prepareDeleteChannel, prepareMyChannelsForTeam, queryAllMyChannelIds, queryChannelsById, queryMyChannel} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, queryCommonSystemValues, queryCurrentTeamId, setCurrentChannelId} from '@queries/servers/system';
|
||||
import {addChannelToTeamHistory, addTeamToTeamHistory, removeChannelFromTeamHistory} from '@queries/servers/team';
|
||||
@@ -61,8 +61,8 @@ export const switchToChannel = async (serverUrl: string, channelId: string, team
|
||||
models.push(...history);
|
||||
}
|
||||
|
||||
const viewedAt = await markChannelAsViewed(serverUrl, channelId, true);
|
||||
if (viewedAt instanceof Model) {
|
||||
const {member: viewedAt} = await markChannelAsViewed(serverUrl, channelId, true);
|
||||
if (viewedAt) {
|
||||
models.push(viewedAt);
|
||||
}
|
||||
|
||||
@@ -155,12 +155,12 @@ export const selectAllMyChannelIds = async (serverUrl: string) => {
|
||||
};
|
||||
|
||||
export const markChannelAsViewed = async (serverUrl: string, channelId: string, prepareRecordsOnly = false) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const member = await queryMyChannel(database, channelId);
|
||||
const member = await queryMyChannel(operator.database, channelId);
|
||||
if (!member) {
|
||||
return {error: 'not a member'};
|
||||
}
|
||||
@@ -170,15 +170,45 @@ export const markChannelAsViewed = async (serverUrl: string, channelId: string,
|
||||
m.mentionsCount = 0;
|
||||
m.manuallyUnread = false;
|
||||
m.viewedAt = member.lastViewedAt;
|
||||
m.lastViewedAt = Date.now();
|
||||
});
|
||||
|
||||
try {
|
||||
if (!prepareRecordsOnly) {
|
||||
const {operator} = DatabaseManager.serverDatabases[serverUrl];
|
||||
await operator.batchRecords([member]);
|
||||
}
|
||||
|
||||
return member;
|
||||
return {member};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const markChannelAsUnread = async (serverUrl: string, channelId: string, messageCount: number, mentionsCount: number, manuallyUnread: boolean, lastViewed: number, prepareRecordsOnly = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const member = await queryMyChannel(operator.database, channelId);
|
||||
if (!member) {
|
||||
return {error: 'not a member'};
|
||||
}
|
||||
|
||||
member.prepareUpdate((m) => {
|
||||
m.viewedAt = lastViewed;
|
||||
m.lastViewedAt = lastViewed;
|
||||
m.messageCount = messageCount;
|
||||
m.mentionsCount = mentionsCount;
|
||||
m.manuallyUnread = manuallyUnread;
|
||||
});
|
||||
|
||||
try {
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([member]);
|
||||
}
|
||||
|
||||
return {member};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
@@ -207,6 +237,67 @@ export const resetMessageCount = async (serverUrl: string, channelId: string) =>
|
||||
}
|
||||
};
|
||||
|
||||
export const storeMyChannelsForTeam = async (serverUrl: string, teamId: string, channels: Channel[], memberships: ChannelMembership[], prepareRecordsOnly = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
const prepare = await prepareMyChannelsForTeam(operator, teamId, channels, memberships);
|
||||
if (prepare) {
|
||||
modelPromises.push(...prepare);
|
||||
}
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat() as Model[];
|
||||
|
||||
if (prepareRecordsOnly) {
|
||||
return {models: flattenedModels};
|
||||
}
|
||||
|
||||
if (flattenedModels?.length > 0) {
|
||||
try {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FAILED TO BATCH CHANNELS');
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
return {models: flattenedModels};
|
||||
};
|
||||
|
||||
export const updateLastPostAt = async (serverUrl: string, channelId: string, lastPostAt: number, prepareRecordsOnly = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const member = await queryMyChannel(operator.database, channelId);
|
||||
if (!member) {
|
||||
return {error: 'not a member'};
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
if (lastPostAt > member.lastPostAt) {
|
||||
member.prepareUpdate((m) => {
|
||||
m.lastPostAt = lastPostAt;
|
||||
});
|
||||
models.push();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!prepareRecordsOnly && models.length) {
|
||||
await operator.batchRecords([member]);
|
||||
}
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {models};
|
||||
};
|
||||
|
||||
export async function updateChannelsDisplayName(serverUrl: string, channels: ChannelModel[], user: UserProfile, prepareRecordsOnly = false) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
|
||||
@@ -100,7 +100,7 @@ export const sendEphemeralPost = async (serverUrl: string, message: string, chan
|
||||
return {post};
|
||||
};
|
||||
|
||||
export const removePost = async (serverUrl: string, post: PostModel) => {
|
||||
export const removePost = async (serverUrl: string, post: PostModel | Post) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -132,6 +132,29 @@ export const selectAttachmentMenuAction = (serverUrl: string, postId: string, ac
|
||||
return postActionWithCookie(serverUrl, postId, actionId, '', selectedOption);
|
||||
};
|
||||
|
||||
export const markPostAsDeleted = async (serverUrl: string, post: Post) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const dbPost = await queryPostById(operator.database, post.id);
|
||||
if (!dbPost) {
|
||||
return {};
|
||||
}
|
||||
|
||||
dbPost.prepareUpdate((p) => {
|
||||
p.deleteAt = Date.now();
|
||||
p.message = '';
|
||||
p.metadata = null;
|
||||
p.props = undefined;
|
||||
});
|
||||
|
||||
operator.batchRecords([dbPost]);
|
||||
|
||||
return {post: dbPost};
|
||||
};
|
||||
|
||||
export const processPostsFetched = async (serverUrl: string, actionType: string, data: PostResponse, fetchOnly = false) => {
|
||||
const order = data.order;
|
||||
const posts = Object.values(data.posts) as Post[];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {Model} from '@nozbe/watermelondb';
|
||||
import {IntlShape} from 'react-intl';
|
||||
|
||||
import {switchToChannel} from '@actions/local/channel';
|
||||
import {storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
|
||||
import {General} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {privateChannelJoinPrompt} from '@helpers/api/channel';
|
||||
@@ -211,26 +211,7 @@ export const fetchMyChannelsForTeam = async (serverUrl: string, teamId: string,
|
||||
}, []);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
if (operator) {
|
||||
const prepare = await prepareMyChannelsForTeam(operator, teamId, channels, memberships);
|
||||
if (prepare) {
|
||||
modelPromises.push(...prepare);
|
||||
}
|
||||
if (modelPromises.length) {
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat() as Model[];
|
||||
if (flattenedModels?.length > 0) {
|
||||
try {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FAILED TO BATCH CHANNELS');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
storeMyChannelsForTeam(serverUrl, teamId, channels, memberships);
|
||||
}
|
||||
|
||||
return {channels, memberships};
|
||||
@@ -255,26 +236,7 @@ export const fetchMyChannel = async (serverUrl: string, teamId: string, channelI
|
||||
]);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
if (operator) {
|
||||
const prepare = await prepareMyChannelsForTeam(operator, teamId, [channel], [member]);
|
||||
if (prepare) {
|
||||
modelPromises.push(...prepare);
|
||||
}
|
||||
if (modelPromises.length) {
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat() as Model[];
|
||||
if (flattenedModels?.length > 0) {
|
||||
try {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FAILED TO BATCH CHANNELS');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
storeMyChannelsForTeam(serverUrl, channel.team_id || teamId, [channel], [member]);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {updateLastPostAt} from '@actions/local/channel';
|
||||
import {processPostsFetched} from '@actions/local/post';
|
||||
import {ActionType, Events, General} from '@constants';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
@@ -94,8 +95,17 @@ export const fetchPostsForChannel = async (serverUrl: string, channelId: string,
|
||||
}
|
||||
}
|
||||
|
||||
let lastPostAt = 0;
|
||||
for (const post of data.posts) {
|
||||
lastPostAt = post.create_at > lastPostAt ? post.create_at : lastPostAt;
|
||||
}
|
||||
const {models: memberModels} = await updateLastPostAt(serverUrl, channelId, lastPostAt, true);
|
||||
if (memberModels?.length) {
|
||||
models.push(...memberModels);
|
||||
}
|
||||
|
||||
if (models.length) {
|
||||
operator.batchRecords(models);
|
||||
await operator.batchRecords(models);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,3 +382,29 @@ export async function getMissingChannelsFromPosts(serverUrl: string, posts: Post
|
||||
channelMemberships,
|
||||
};
|
||||
}
|
||||
|
||||
export const fetchPostById = async (serverUrl: string, postId: string, fetchOnly = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const post = await client.getPost(postId);
|
||||
if (!fetchOnly) {
|
||||
operator.handlePosts({actionType: ActionType.POSTS.RECEIVED_NEW, order: [post.id], posts: [post]});
|
||||
}
|
||||
|
||||
return {post};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,6 +18,8 @@ import {queryCurrentUser, queryUserById} from '@queries/servers/user';
|
||||
import {dismissAllModals, popToRoot} from '@screens/navigation';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
|
||||
import type {WebSocketMessage} from '@typings/api/websocket';
|
||||
|
||||
export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
@@ -147,7 +149,7 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
|
||||
database.operator.batchRecords(models);
|
||||
}
|
||||
|
||||
export async function handleChannelDeletedEvent(serverUrl: string, msg: any) {
|
||||
export async function handleChannelDeletedEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
return;
|
||||
|
||||
@@ -20,11 +20,13 @@ import {queryCommonSystemValues, queryConfig, queryWebSocketLastDisconnected} fr
|
||||
import {queryCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import {handleChannelDeletedEvent, handleUserAddedToChannelEvent, handleUserRemovedFromChannelEvent} from './channel';
|
||||
import {handleNewPostEvent, handlePostDeleted, handlePostEdited, handlePostUnread} from './posts';
|
||||
import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences';
|
||||
import {handleLeaveTeamEvent} from './teams';
|
||||
import {handleUserUpdatedEvent} from './users';
|
||||
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
import type {WebSocketMessage} from '@typings/api/websocket';
|
||||
|
||||
export async function handleFirstConnect(serverUrl: string) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
@@ -127,7 +129,7 @@ async function doReconnect(serverUrl: string) {
|
||||
} else if (!channelStillExist ||
|
||||
(!viewArchivedChannels && channelStillExist.delete_at !== 0)
|
||||
) {
|
||||
handleChannelDeletedEvent(serverUrl, {data: {user_id: currentUserId, channel_id: currentChannelId}});
|
||||
handleChannelDeletedEvent(serverUrl, {data: {user_id: currentUserId, channel_id: currentChannelId}} as WebSocketMessage);
|
||||
} else {
|
||||
// TODO Differentiate between post and thread, to fetch the thread posts
|
||||
fetchPostsSince(serverUrl, currentChannelId, lastDisconnectedAt);
|
||||
@@ -136,7 +138,7 @@ async function doReconnect(serverUrl: string) {
|
||||
|
||||
// TODO Consider global thread screen to update global threads
|
||||
} else if (!teamMembershipsError) {
|
||||
handleLeaveTeamEvent(serverUrl, {data: {user_id: currentUserId, team_id: currentTeamId}});
|
||||
handleLeaveTeamEvent(serverUrl, {data: {user_id: currentUserId, team_id: currentTeamId}} as WebSocketMessage);
|
||||
}
|
||||
|
||||
fetchRoles(serverUrl, teamMemberships, channelMemberships);
|
||||
@@ -147,25 +149,25 @@ async function doReconnect(serverUrl: string) {
|
||||
updateAllUsersSinceLastDisconnect(serverUrl);
|
||||
}
|
||||
|
||||
export async function handleEvent(serverUrl: string, msg: any) {
|
||||
export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
switch (msg.event) {
|
||||
case WebsocketEvents.POSTED:
|
||||
case WebsocketEvents.EPHEMERAL_MESSAGE:
|
||||
handleNewPostEvent(serverUrl, msg);
|
||||
break;
|
||||
|
||||
//return dispatch(handleNewPostEvent(msg));
|
||||
case WebsocketEvents.POST_EDITED:
|
||||
handlePostEdited(serverUrl, msg);
|
||||
break;
|
||||
|
||||
//return dispatch(handlePostEdited(msg));
|
||||
case WebsocketEvents.POST_DELETED:
|
||||
handlePostDeleted(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// return dispatch(handlePostDeleted(msg));
|
||||
case WebsocketEvents.POST_UNREAD:
|
||||
handlePostUnread(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// return dispatch(handlePostUnread(msg));
|
||||
case WebsocketEvents.LEAVE_TEAM:
|
||||
handleLeaveTeamEvent(serverUrl, msg);
|
||||
break;
|
||||
|
||||
209
app/actions/websocket/posts.ts
Normal file
209
app/actions/websocket/posts.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Model} from '@nozbe/watermelondb';
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {storeMyChannelsForTeam, markChannelAsUnread, markChannelAsViewed, updateLastPostAt} from '@actions/local/channel';
|
||||
import {markPostAsDeleted} from '@actions/local/post';
|
||||
import {fetchMyChannel, markChannelAsRead} from '@actions/remote/channel';
|
||||
import {fetchPostAuthors, fetchPostById} from '@actions/remote/post';
|
||||
import {ActionType, Events} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {queryMyChannel} from '@queries/servers/channel';
|
||||
import {queryPostById} from '@queries/servers/post';
|
||||
import {queryCurrentChannelId, queryCurrentUserId} from '@queries/servers/system';
|
||||
import {isFromWebhook, isSystemMessage, shouldIgnorePost} from '@utils/post';
|
||||
|
||||
import type {WebSocketMessage} from '@typings/api/websocket';
|
||||
|
||||
export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
let post: Post;
|
||||
try {
|
||||
post = JSON.parse(msg.data.post);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const currentUserId = await queryCurrentUserId(operator.database);
|
||||
|
||||
const existing = await queryPostById(operator.database, post.pending_post_id);
|
||||
|
||||
if (existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
|
||||
const postModels = await operator.handlePosts({
|
||||
actionType: ActionType.POSTS.RECEIVED_NEW,
|
||||
order: [post.id],
|
||||
posts: [post],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
if (postModels?.length) {
|
||||
models.push(...postModels);
|
||||
}
|
||||
|
||||
// Ensure the channel membership
|
||||
let myChannel = await queryMyChannel(operator.database, post.channel_id);
|
||||
if (myChannel) {
|
||||
// Do not batch lastPostAt update since we may be modifying the member later for unreads
|
||||
await updateLastPostAt(serverUrl, post.channel_id, post.create_at, false);
|
||||
} else {
|
||||
const myChannelRequest = await fetchMyChannel(serverUrl, '', post.channel_id, true);
|
||||
if (myChannelRequest.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to have this on the database so we can make any needed update later
|
||||
const myChannelModels = await storeMyChannelsForTeam(serverUrl, '', myChannelRequest.channels!, myChannelRequest.memberships!, false);
|
||||
if (myChannelModels.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
myChannel = await queryMyChannel(operator.database, post.channel_id);
|
||||
if (!myChannel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have the root post for this post, fetch it from the server
|
||||
if (post.root_id) {
|
||||
const rootPost = await queryPostById(operator.database, post.root_id);
|
||||
|
||||
if (!rootPost) {
|
||||
fetchPostById(serverUrl, post.root_id);
|
||||
}
|
||||
}
|
||||
|
||||
const currentChannelId = await queryCurrentChannelId(operator.database);
|
||||
|
||||
if (post.channel_id === currentChannelId) {
|
||||
const data = {
|
||||
channelId: post.channel_id,
|
||||
rootId: post.root_id,
|
||||
userId: post.user_id,
|
||||
now: Date.now(),
|
||||
};
|
||||
DeviceEventEmitter.emit(Events.USER_STOP_TYPING, data);
|
||||
}
|
||||
|
||||
const {authors} = await fetchPostAuthors(serverUrl, [post], true);
|
||||
if (authors?.length) {
|
||||
const authorsModels = await operator.handleUsers({users: authors, prepareRecordsOnly: true});
|
||||
if (authorsModels.length) {
|
||||
models.push(...authorsModels);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Thread related functionality: https://mattermost.atlassian.net/browse/MM-41084
|
||||
//const viewingGlobalThreads = getViewingGlobalThreads(state);
|
||||
// const collapsedThreadsEnabled = isCollapsedThreadsEnabled(state);
|
||||
// actions.push(receivedNewPost(post, collapsedThreadsEnabled));
|
||||
if (!shouldIgnorePost(post)) {
|
||||
let markAsViewed = false;
|
||||
let markAsRead = false;
|
||||
|
||||
if (!myChannel.manuallyUnread) {
|
||||
if (
|
||||
post.user_id === currentUserId &&
|
||||
!isSystemMessage(post) &&
|
||||
!isFromWebhook(post)
|
||||
) {
|
||||
markAsViewed = true;
|
||||
markAsRead = false;
|
||||
} else if ((post.channel_id === currentChannelId)) { // TODO: THREADS && !viewingGlobalThreads) {
|
||||
// Don't mark as read if we're in global threads screen
|
||||
// the currentChannelId still refers to previously viewed channel
|
||||
markAsViewed = false;
|
||||
markAsRead = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (markAsRead) {
|
||||
markChannelAsRead(serverUrl, post.channel_id);
|
||||
} else if (markAsViewed) {
|
||||
const {member: viewedAt} = await markChannelAsViewed(serverUrl, post.channel_id, true);
|
||||
if (viewedAt) {
|
||||
models.push(viewedAt);
|
||||
}
|
||||
} else {
|
||||
const hasMentions = msg.data.mentions.includes(currentUserId);
|
||||
const {member: unreadAt} = await markChannelAsUnread(
|
||||
serverUrl,
|
||||
post.channel_id,
|
||||
myChannel.messageCount + 1,
|
||||
myChannel.mentionsCount + (hasMentions ? 1 : 0),
|
||||
false,
|
||||
myChannel.lastViewedAt,
|
||||
true,
|
||||
);
|
||||
if (unreadAt) {
|
||||
models.push(unreadAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator.batchRecords(models);
|
||||
}
|
||||
|
||||
export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
let post: Post;
|
||||
try {
|
||||
post = JSON.parse(msg.data.post);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
|
||||
const {authors} = await fetchPostAuthors(serverUrl, [post], true);
|
||||
if (authors?.length) {
|
||||
const authorsModels = await operator.handleUsers({users: authors, prepareRecordsOnly: true});
|
||||
if (authorsModels.length) {
|
||||
models.push(...authorsModels);
|
||||
}
|
||||
}
|
||||
|
||||
const postModels = await operator.handlePosts({
|
||||
actionType: ActionType.POSTS.RECEIVED_NEW,
|
||||
order: [post.id],
|
||||
posts: [post],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
if (postModels.length) {
|
||||
models.push(...postModels);
|
||||
}
|
||||
|
||||
if (models.length) {
|
||||
operator.batchRecords(models);
|
||||
}
|
||||
}
|
||||
|
||||
export function handlePostDeleted(serverUrl: string, msg: WebSocketMessage) {
|
||||
try {
|
||||
const data: Post = JSON.parse(msg.data.post);
|
||||
markPostAsDeleted(serverUrl, data);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export async function handlePostUnread(serverUrl: string, msg: WebSocketMessage) {
|
||||
const {channels} = await fetchMyChannel(serverUrl, msg.broadcast.team_id, msg.broadcast.channel_id, true);
|
||||
const channel = channels?.[0];
|
||||
const postNumber = channel?.total_msg_count;
|
||||
const delta = postNumber ? postNumber - msg.data.msg_count : msg.data.msg_count;
|
||||
markChannelAsUnread(serverUrl, msg.broadcast.channel_id, delta, msg.data.mention_count, true, msg.data.last_viewed_at);
|
||||
}
|
||||
@@ -14,7 +14,9 @@ import {queryLastTeam} from '@queries/servers/team';
|
||||
import {queryCurrentUser} from '@queries/servers/user';
|
||||
import {dismissAllModals, popToRoot} from '@screens/navigation';
|
||||
|
||||
export async function handleLeaveTeamEvent(serverUrl: string, msg: any) {
|
||||
import type {WebSocketMessage} from '@typings/api/websocket';
|
||||
|
||||
export async function handleLeaveTeamEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl];
|
||||
if (!database) {
|
||||
return;
|
||||
|
||||
@@ -163,4 +163,4 @@ const withPost = withObservables(['post'], ({database, post}: {post: PostModel}
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(withPost(React.memo(Avatar)));
|
||||
export default withDatabase(withPost(Avatar));
|
||||
|
||||
@@ -234,4 +234,4 @@ const withChannelType = withObservables(['post'], ({database, post}: WithDatabas
|
||||
),
|
||||
}));
|
||||
|
||||
export default withDatabase(withChannelType(React.memo(AddMembers)));
|
||||
export default withDatabase(withChannelType(AddMembers));
|
||||
|
||||
@@ -141,4 +141,4 @@ const withTeamId = withObservables(['post'], ({post}: {post: PostModel}) => ({
|
||||
),
|
||||
}));
|
||||
|
||||
export default withTeamId(React.memo(ButtonBinding));
|
||||
export default withTeamId(ButtonBinding);
|
||||
|
||||
@@ -114,4 +114,4 @@ const withTeamId = withObservables(['post'], ({post}: {post: PostModel}) => ({
|
||||
),
|
||||
}));
|
||||
|
||||
export default withTeamId(React.memo(MenuBinding));
|
||||
export default withTeamId(MenuBinding);
|
||||
|
||||
@@ -110,4 +110,4 @@ const Message = ({currentUser, groupsForPosts, highlight, isEdited, isPendingOrF
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Message);
|
||||
export default Message;
|
||||
|
||||
@@ -145,4 +145,4 @@ const Header = (props: HeaderProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Header);
|
||||
export default Header;
|
||||
|
||||
@@ -299,4 +299,4 @@ const Post = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Post);
|
||||
export default Post;
|
||||
|
||||
@@ -14,4 +14,6 @@ export default keyMirror({
|
||||
SERVER_LOGOUT: null,
|
||||
SERVER_VERSION_CHANGED: null,
|
||||
TEAM_LOAD_ERROR: null,
|
||||
USER_TYPING: null,
|
||||
USER_STOP_TYPING: null,
|
||||
});
|
||||
|
||||
@@ -46,4 +46,18 @@ export default {
|
||||
PostTypes.LEAVE_TEAM,
|
||||
PostTypes.REMOVE_FROM_TEAM,
|
||||
],
|
||||
IGNORE_POST_TYPES: [
|
||||
PostTypes.ADD_REMOVE,
|
||||
PostTypes.ADD_TO_CHANNEL,
|
||||
PostTypes.CHANNEL_DELETED,
|
||||
PostTypes.CHANNEL_UNARCHIVED,
|
||||
PostTypes.JOIN_LEAVE,
|
||||
PostTypes.JOIN_CHANNEL,
|
||||
PostTypes.LEAVE_CHANNEL,
|
||||
PostTypes.REMOVE_FROM_CHANNEL,
|
||||
PostTypes.JOIN_TEAM,
|
||||
PostTypes.LEAVE_TEAM,
|
||||
PostTypes.ADD_TO_TEAM,
|
||||
PostTypes.REMOVE_FROM_TEAM,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -65,6 +65,11 @@ export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, tea
|
||||
pinned_post_count = storedInfo.pinnedPostCount;
|
||||
}
|
||||
|
||||
const member = memberships.find((m) => m.channel_id === c.id);
|
||||
if (member) {
|
||||
member.last_post_at = c.last_post_at;
|
||||
}
|
||||
|
||||
channelInfos.push({
|
||||
id: c.id,
|
||||
header: c.header,
|
||||
|
||||
@@ -51,7 +51,6 @@ const enhanced = withObservables(['channelId', 'forceQueryAfterAppState'], ({dat
|
||||
const {earliest, latest} = postsInChannel[0];
|
||||
return database.get<PostModel>(POST).query(
|
||||
Q.and(
|
||||
Q.where('delete_at', 0),
|
||||
Q.where('channel_id', channelId),
|
||||
Q.where('create_at', Q.between(earliest, latest)),
|
||||
),
|
||||
|
||||
@@ -28,7 +28,7 @@ export function areConsecutivePosts(post: PostModel, previousPost: PostModel) {
|
||||
return consecutive;
|
||||
}
|
||||
|
||||
export function isFromWebhook(post: PostModel): boolean {
|
||||
export function isFromWebhook(post: PostModel | Post): boolean {
|
||||
return post.props && post.props.from_webhook === 'true';
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ export function isPostEphemeral(post: PostModel): boolean {
|
||||
}
|
||||
|
||||
export function isPostPendingOrFailed(post: PostModel): boolean {
|
||||
return post.pendingPostId === post.id || post.props.failed;
|
||||
return post.pendingPostId === post.id || post.props?.failed;
|
||||
}
|
||||
|
||||
export function isSystemMessage(post: PostModel): boolean {
|
||||
export function isSystemMessage(post: PostModel | Post): boolean {
|
||||
return Boolean(post.type && post.type?.startsWith(Post.POST_TYPES.SYSTEM_MESSAGE_PREFIX));
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ export const getMentionKeysForPost = (user: UserModel, post: PostModel, groups:
|
||||
return keys;
|
||||
};
|
||||
|
||||
export function shouldIgnorePost(post: Post): boolean {
|
||||
return Post.IGNORE_POST_TYPES.includes(post.type);
|
||||
}
|
||||
|
||||
export const sortPostsByNewest = (posts: PostModel[]) => {
|
||||
return posts.sort((a, b) => {
|
||||
if (a.createAt > b.createAt) {
|
||||
|
||||
1
types/api/posts.d.ts
vendored
1
types/api/posts.d.ts
vendored
@@ -64,7 +64,6 @@ type Post = {
|
||||
failed?: boolean;
|
||||
user_activity_posts?: Post[];
|
||||
state?: 'DELETED';
|
||||
ownPost?: boolean;
|
||||
prev_post_id?: string;
|
||||
participants: null|string[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user