diff --git a/actions/websocket_actions.jsx b/actions/websocket_actions.jsx index 1bed58a68202..14aa7e41e049 100644 --- a/actions/websocket_actions.jsx +++ b/actions/websocket_actions.jsx @@ -41,6 +41,7 @@ import { handleThreadArrived, handleAllThreadsInChannelMarkedRead, updateThreadRead, + decrementThreadCounts, } from 'mattermost-redux/actions/threads'; import {setServerVersion} from 'mattermost-redux/actions/general'; @@ -684,11 +685,17 @@ export function handlePostEditEvent(msg) { async function handlePostDeleteEvent(msg) { const post = JSON.parse(msg.data.post); + const state = getState(); + const collapsedThreads = isCollapsedThreadsEnabled(state); + + if (!post.root_id && collapsedThreads) { + dispatch(decrementThreadCounts(post)); + } + dispatch(postDeleted(post)); // update thread when a comment is deleted and CRT is on - const state = getState(); - if (post.root_id && isCollapsedThreadsEnabled(state)) { + if (post.root_id && collapsedThreads) { const thread = getThread(state, post.root_id); if (thread) { const userId = getCurrentUserId(state); diff --git a/packages/mattermost-redux/src/action_types/threads.ts b/packages/mattermost-redux/src/action_types/threads.ts index 123bfe9f7a64..54f58f6ccc11 100644 --- a/packages/mattermost-redux/src/action_types/threads.ts +++ b/packages/mattermost-redux/src/action_types/threads.ts @@ -9,4 +9,5 @@ export default keyMirror({ FOLLOW_CHANGED_THREAD: null, READ_CHANGED_THREAD: null, ALL_TEAM_THREADS_READ: null, + DECREMENT_THREAD_COUNTS: null, }); diff --git a/packages/mattermost-redux/src/actions/posts.ts b/packages/mattermost-redux/src/actions/posts.ts index e431d58d76f3..944d4b803afe 100644 --- a/packages/mattermost-redux/src/actions/posts.ts +++ b/packages/mattermost-redux/src/actions/posts.ts @@ -29,6 +29,7 @@ import {bindClientFunc, forceLogoutIfNecessary} from './helpers'; import {logError} from './errors'; import {systemEmojis, getCustomEmojiByName, getCustomEmojisByName} from './emojis'; import {selectChannel} from './channels'; +import {decrementThreadCounts} from './threads'; // receivedPost should be dispatched after a single post from the server. This typically happens when an existing post // is updated. @@ -382,6 +383,9 @@ export function deletePost(post: ExtendedPost) { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); const delPost = {...post}; + if (!post.root_id && isCollapsedThreadsEnabled(state)) { + dispatch(decrementThreadCounts(post)); + } if (delPost.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && delPost.system_post_ids) { delPost.system_post_ids.forEach((systemPostId) => { const systemPost = Selectors.getPost(state, systemPostId); diff --git a/packages/mattermost-redux/src/actions/threads.ts b/packages/mattermost-redux/src/actions/threads.ts index 8f29e97d8530..a67d4acde1fb 100644 --- a/packages/mattermost-redux/src/actions/threads.ts +++ b/packages/mattermost-redux/src/actions/threads.ts @@ -12,13 +12,15 @@ import {DispatchFunc, GetStateFunc, batchActions} from 'mattermost-redux/types/a import type {UserThread, UserThreadList} from 'mattermost-redux/types/threads'; +import {Post} from 'mattermost-redux/types/posts'; + import {getMissingProfilesByIds} from 'mattermost-redux/actions/users'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {getThreadsInChannel} from 'mattermost-redux/selectors/entities/threads'; +import {getThreadsInChannel, getThread as getThreadSelector} from 'mattermost-redux/selectors/entities/threads'; import {getChannel} from 'mattermost-redux/selectors/entities/channels'; @@ -27,6 +29,8 @@ import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/pre import {logError} from './errors'; import {forceLogoutIfNecessary} from './helpers'; +type ExtendedPost = Post & { system_post_ids?: string[] }; + export function getThreads(userId: string, teamId: string, {before = '', after = '', perPage = ThreadConstants.THREADS_CHUNK_SIZE, unread = false, totalsOnly = false} = {}) { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { let userThreadList: undefined | UserThreadList; @@ -249,3 +253,25 @@ export function handleAllThreadsInChannelMarkedRead(dispatch: DispatchFunc, getS dispatch(batchActions(actions)); } + +export function decrementThreadCounts(post: ExtendedPost) { + return (dispatch: DispatchFunc, getState: GetStateFunc) => { + const state = getState(); + const thread = getThreadSelector(state, post.id); + + if (!thread || (thread.unread_replies === 0 && thread.unread_mentions === 0)) { + return {data: false}; + } + + const channel = getChannel(state, post.channel_id); + const teamId = channel?.team_id || getCurrentTeamId(state); + + return dispatch({ + type: ThreadTypes.DECREMENT_THREAD_COUNTS, + teamId, + replies: thread.unread_replies, + mentions: thread.unread_mentions, + channelType: channel.type, + }); + }; +} diff --git a/packages/mattermost-redux/src/reducers/entities/threads/counts.ts b/packages/mattermost-redux/src/reducers/entities/threads/counts.ts index ee5e9693c33f..7d17b7a8686a 100644 --- a/packages/mattermost-redux/src/reducers/entities/threads/counts.ts +++ b/packages/mattermost-redux/src/reducers/entities/threads/counts.ts @@ -6,6 +6,8 @@ import {GenericAction} from 'mattermost-redux/types/actions'; import {ThreadsState, UserThread} from 'mattermost-redux/types/threads'; import {Team, TeamUnread} from 'mattermost-redux/types/teams'; +import Constants from 'utils/constants'; + import {ExtraData} from './types'; function handleAllTeamThreadsRead(state: ThreadsState['counts'], action: GenericAction): ThreadsState['counts'] { @@ -93,6 +95,23 @@ function handleLeaveChannel(state: ThreadsState['counts'] = {}, action: GenericA }; } +function handleDecrementThreadCounts(state: ThreadsState['counts'], action: GenericAction) { + const {teamId, replies, mentions} = action; + const counts = state[teamId]; + if (!counts) { + return state; + } + + return { + ...state, + [teamId]: { + total: Math.max(counts.total - 1, 0), + total_unread_mentions: Math.max(counts.total_unread_mentions - mentions, 0), + total_unread_threads: Math.max(counts.total_unread_threads - replies, 0), + }, + }; +} + export function countsIncludingDirectReducer(state: ThreadsState['counts'] = {}, action: GenericAction, extra: ExtraData) { switch (action.type) { case ThreadTypes.ALL_TEAM_THREADS_READ: @@ -129,6 +148,8 @@ export function countsIncludingDirectReducer(state: ThreadsState['counts'] = {}, total_unread_mentions: action.data.total_unread_mentions, }, }; + case ThreadTypes.DECREMENT_THREAD_COUNTS: + return handleDecrementThreadCounts(state, action); case UserTypes.LOGOUT_SUCCESS: return {}; } @@ -163,6 +184,13 @@ export function countsReducer(state: ThreadsState['counts'] = {}, action: Generi }, {}), }; } + case ThreadTypes.DECREMENT_THREAD_COUNTS: { + const {channelType} = action; + if (channelType === Constants.DM_CHANNEL || channelType === Constants.GM_CHANNEL) { + return state; + } + return handleDecrementThreadCounts(state, action); + } } return state; }