MM-52797 : Drafts badge doesn't clear when parent message deleted and removed (#23636)

* yup

* int

* c

* ok

* Update posts.ts

* actual change

* a

* Update actions.ts

* ty

* Update drafts_link.tsx

* revert to old function name

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
M-ZubairAhmed
2023-06-10 01:52:45 +05:30
коммит произвёл GitHub
родитель c0ee02356e
Коммит adb248497d
15 изменённых файлов: 129 добавлений и 125 удалений

Просмотреть файл

@@ -299,6 +299,7 @@ export function markMostRecentPostInChannelAsUnread(channelId: string) {
}; };
} }
// Action called by DeletePostModal when the post is deleted
export function deleteAndRemovePost(post: Post) { export function deleteAndRemovePost(post: Post) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => { return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const {error} = await dispatch(PostActions.deletePost(post)); const {error} = await dispatch(PostActions.deletePost(post));

Просмотреть файл

@@ -37,6 +37,7 @@ type Draft = {
*/ */
export function getDrafts(teamId: string) { export function getDrafts(teamId: string) {
const getLocalDrafts = makeGetDrafts(false); const getLocalDrafts = makeGetDrafts(false);
return async (dispatch: DispatchFunc, getState: GetStateFunc) => { return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const state = getState() as GlobalState; const state = getState() as GlobalState;

Просмотреть файл

@@ -4,17 +4,24 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import React from 'react'; import React from 'react';
import {ModalData} from 'types/actions.js';
import {isNil} from 'lodash'; import {isNil} from 'lodash';
import {PreferenceType} from '@mattermost/types/preferences';
import {Group, GroupSource} from '@mattermost/types/groups';
import {ChannelMemberCountsByGroup} from '@mattermost/types/channels';
import {Emoji} from '@mattermost/types/emojis';
import {ServerError} from '@mattermost/types/errors';
import {FileInfo} from '@mattermost/types/files';
import {ActionResult} from 'mattermost-redux/types/actions';
import {sortFileInfos} from 'mattermost-redux/utils/file_utils'; import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
import * as GlobalActions from 'actions/global_actions'; import * as GlobalActions from 'actions/global_actions';
import {PostDraft} from 'types/store/draft';
import {ModalData} from 'types/actions';
import Constants, {AdvancedTextEditor as AdvancedTextEditorConst, Locations, ModalIdentifiers, Preferences} from 'utils/constants'; import Constants, {AdvancedTextEditor as AdvancedTextEditorConst, Locations, ModalIdentifiers, Preferences} from 'utils/constants';
import {PreferenceType} from '@mattermost/types/preferences';
import * as Keyboard from 'utils/keyboard'; import * as Keyboard from 'utils/keyboard';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
@@ -28,18 +35,6 @@ import {
mentionsMinusSpecialMentionsInText, mentionsMinusSpecialMentionsInText,
} from 'utils/post_utils'; } from 'utils/post_utils';
import {getTable, hasHtmlLink, formatMarkdownMessage, isGitHubCodeBlock, formatGithubCodePaste, isHttpProtocol, isHttpsProtocol} from 'utils/paste'; import {getTable, hasHtmlLink, formatMarkdownMessage, isGitHubCodeBlock, formatGithubCodePaste, isHttpProtocol, isHttpsProtocol} from 'utils/paste';
import NotifyConfirmModal from 'components/notify_confirm_modal';
import {FileUpload as FileUploadClass} from 'components/file_upload/file_upload';
import PostDeletedModal from 'components/post_deleted_modal';
import {PostDraft} from 'types/store/draft';
import {Group, GroupSource} from '@mattermost/types/groups';
import {ChannelMemberCountsByGroup} from '@mattermost/types/channels';
import {FilePreviewInfo} from 'components/file_preview/file_preview';
import {Emoji} from '@mattermost/types/emojis';
import {ActionResult} from 'mattermost-redux/types/actions';
import {ServerError} from '@mattermost/types/errors';
import {FileInfo} from '@mattermost/types/files';
import EmojiMap from 'utils/emoji_map'; import EmojiMap from 'utils/emoji_map';
import { import {
applyLinkMarkdown, applyLinkMarkdown,
@@ -47,10 +42,14 @@ import {
applyMarkdown, applyMarkdown,
ApplyMarkdownOptions, ApplyMarkdownOptions,
} from 'utils/markdown/apply_markdown'; } from 'utils/markdown/apply_markdown';
import AdvancedTextEditor from '../advanced_text_editor/advanced_text_editor';
import {TextboxClass, TextboxElement} from '../textbox';
import FileLimitStickyBanner from '../file_limit_sticky_banner'; import NotifyConfirmModal from 'components/notify_confirm_modal';
import {FileUpload as FileUploadClass} from 'components/file_upload/file_upload';
import PostDeletedModal from 'components/post_deleted_modal';
import {FilePreviewInfo} from 'components/file_preview/file_preview';
import AdvancedTextEditor from 'components/advanced_text_editor/advanced_text_editor';
import {TextboxClass, TextboxElement} from 'components/textbox';
import FileLimitStickyBanner from 'components/file_limit_sticky_banner';
const KeyCodes = Constants.KeyCodes; const KeyCodes = Constants.KeyCodes;
@@ -350,7 +349,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
}; };
saveDraftOnUnmount = () => { saveDraftOnUnmount = () => {
if (!this.isDraftEdited || !this.state.draft) { if (!this.isDraftEdited || !this.state.draft || this.props.rootDeleted) {
return; return;
} }

Просмотреть файл

@@ -34,7 +34,7 @@ type Props = {
type: 'channel' | 'thread'; type: 'channel' | 'thread';
user: UserProfile; user: UserProfile;
value: PostDraft; value: PostDraft;
isRemote: boolean; isRemote?: boolean;
} }
function ChannelDraft({ function ChannelDraft({

Просмотреть файл

@@ -14,7 +14,7 @@ type Props = {
status: UserStatus['status']; status: UserStatus['status'];
displayName: string; displayName: string;
draft: Draft; draft: Draft;
isRemote: boolean; isRemote?: boolean;
} }
function DraftRow({draft, user, status, displayName, isRemote}: Props) { function DraftRow({draft, user, status, displayName, isRemote}: Props) {

Просмотреть файл

@@ -77,7 +77,7 @@ function Drafts({
key={d.key} key={d.key}
displayName={displayName} displayName={displayName}
draft={d} draft={d}
isRemote={draftRemotes[d.key]} isRemote={draftRemotes?.[d.key]}
user={user} user={user}
status={status} status={status}
/> />

Просмотреть файл

@@ -1,56 +1,43 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {memo, useCallback, useEffect} from 'react'; import React, {memo, useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux'; import {useSelector, useDispatch} from 'react-redux';
import {NavLink, useRouteMatch} from 'react-router-dom'; import {NavLink, useRouteMatch} from 'react-router-dom';
import {useIntl} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {localDraftsAreEnabled, syncedDraftsAreAllowedAndEnabled} from 'mattermost-redux/selectors/entities/preferences'; import {localDraftsAreEnabled, syncedDraftsAreAllowedAndEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getDrafts} from 'actions/views/drafts'; import {getDrafts} from 'actions/views/drafts';
import {closeRightHandSide} from 'actions/views/rhs';
import {makeGetDraftsCount} from 'selectors/drafts'; import {makeGetDraftsCount} from 'selectors/drafts';
import {getIsRhsOpen, getRhsState} from 'selectors/rhs';
import {RHSStates} from 'utils/constants';
import ChannelMentionBadge from 'components/sidebar/sidebar_channel/channel_mention_badge'; import ChannelMentionBadge from 'components/sidebar/sidebar_channel/channel_mention_badge';
import DraftsTourTip from 'components/drafts/drafts_link/drafts_tour_tip/drafts_tour_tip';
import './drafts_link.scss'; import './drafts_link.scss';
import DraftsTourTip from './drafts_tour_tip/drafts_tour_tip';
const getDraftsCount = makeGetDraftsCount(); const getDraftsCount = makeGetDraftsCount();
function DraftsLink() { function DraftsLink() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const localDraftsEnabled = useSelector(localDraftsAreEnabled); const localDraftsEnabled = useSelector(localDraftsAreEnabled);
const syncedDraftsAllowedAndEnabled = useSelector(syncedDraftsAreAllowedAndEnabled); const syncedDraftsAllowedAndEnabled = useSelector(syncedDraftsAreAllowedAndEnabled);
const {formatMessage} = useIntl();
const {url} = useRouteMatch();
const match = useRouteMatch('/:team/drafts');
const count = useSelector(getDraftsCount); const count = useSelector(getDraftsCount);
const teamId = useSelector(getCurrentTeamId); const teamId = useSelector(getCurrentTeamId);
const rhsOpen = useSelector(getIsRhsOpen);
const rhsState = useSelector(getRhsState); const {url} = useRouteMatch();
const isDraftUrlMatch = useRouteMatch('/:team/drafts');
useEffect(() => { useEffect(() => {
if (syncedDraftsAllowedAndEnabled) { if (syncedDraftsAllowedAndEnabled) {
dispatch(getDrafts(teamId)); dispatch(getDrafts(teamId));
} }
}, [teamId, dispatch, syncedDraftsAllowedAndEnabled]); }, [teamId, syncedDraftsAllowedAndEnabled]);
const openDrafts = useCallback((e) => { if (!localDraftsEnabled || (!count && !isDraftUrlMatch)) {
e.stopPropagation();
if (rhsOpen && rhsState === RHSStates.EDIT_HISTORY) {
dispatch(closeRightHandSide());
}
}, [rhsOpen, rhsState]);
if (!localDraftsEnabled || (!count && !match)) {
return null; return null;
} }
@@ -59,10 +46,9 @@ function DraftsLink() {
<li <li
className='SidebarChannel' className='SidebarChannel'
tabIndex={-1} tabIndex={-1}
id={'sidebar-drafts-button'} id='sidebar-drafts-button'
> >
<NavLink <NavLink
onClick={openDrafts}
to={`${url}/drafts`} to={`${url}/drafts`}
id='sidebarItem_drafts' id='sidebarItem_drafts'
activeClassName='active' activeClassName='active'
@@ -76,7 +62,10 @@ function DraftsLink() {
/> />
<div className='SidebarChannelLinkLabel_wrapper'> <div className='SidebarChannelLinkLabel_wrapper'>
<span className='SidebarChannelLinkLabel sidebar-item__name'> <span className='SidebarChannelLinkLabel sidebar-item__name'>
{formatMessage({id: 'drafts.sidebarLink', defaultMessage: 'Drafts'})} <FormattedMessage
id='drafts.sidebarLink'
defaultMessage='Drafts'
/>
</span> </span>
</div> </div>
{count > 0 && ( {count > 0 && (

Просмотреть файл

@@ -33,7 +33,7 @@ type Props = {
type: 'channel' | 'thread'; type: 'channel' | 'thread';
user: UserProfile; user: UserProfile;
value: PostDraft; value: PostDraft;
isRemote: boolean; isRemote?: boolean;
} }
function ThreadDraft({ function ThreadDraft({

Просмотреть файл

@@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {DispatchFunc} from 'mattermost-redux/types/actions';
import {ExtendedPost, removePost} from 'mattermost-redux/actions/posts';
import {isThreadOpen} from 'selectors/views/threads';
import {getGlobalItem} from 'selectors/storage';
import {removeDraft} from 'actions/views/drafts';
import {closeRightHandSide} from 'actions/views/rhs';
import {StoragePrefixes} from 'utils/constants';
import {GlobalState} from 'types/store';
/**
* This action is called when the deleted post which is shown as 'deleted' in the RHS is then removed from the channel manually.
* @param post Deleted post
*/
export function removePostCloseRHSDeleteDraft(post: ExtendedPost) {
return (dispatch: DispatchFunc, getState: () => GlobalState) => {
if (isThreadOpen(getState(), post.id)) {
dispatch(closeRightHandSide());
}
const draftKey = `${StoragePrefixes.COMMENT_DRAFT}${post.id}`;
if (getGlobalItem(getState(), draftKey, null)) {
dispatch(removeDraft(draftKey, post.channel_id, post.id));
}
return dispatch(removePost(post));
};
}

Просмотреть файл

@@ -4,10 +4,12 @@
import {connect, ConnectedProps} from 'react-redux'; import {connect, ConnectedProps} from 'react-redux';
import {AnyAction, bindActionCreators, Dispatch} from 'redux'; import {AnyAction, bindActionCreators, Dispatch} from 'redux';
import {Emoji} from '@mattermost/types/emojis';
import {Post} from '@mattermost/types/posts';
import {setActionsMenuInitialisationState} from 'mattermost-redux/actions/preferences'; import {setActionsMenuInitialisationState} from 'mattermost-redux/actions/preferences';
import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getPost, makeGetCommentCountForPost, makeIsPostCommentMention, isPostAcknowledgementsEnabled, isPostPriorityEnabled, UserActivityPost} from 'mattermost-redux/selectors/entities/posts'; import {getPost, makeGetCommentCountForPost, makeIsPostCommentMention, isPostAcknowledgementsEnabled, isPostPriorityEnabled, UserActivityPost} from 'mattermost-redux/selectors/entities/posts';
import { import {
get, get,
getBool, getBool,
@@ -15,11 +17,10 @@ import {
} from 'mattermost-redux/selectors/entities/preferences'; } from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeam, getTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentTeam, getTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users'; import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
import {getDirectTeammate} from 'mattermost-redux/selectors/entities/channels';
import {General} from 'mattermost-redux/constants';
import {Emoji} from '@mattermost/types/emojis';
import {Post} from '@mattermost/types/posts';
import {closeRightHandSide, selectPost, setRhsExpanded, selectPostCard, selectPostFromRightHandSideSearch} from 'actions/views/rhs'; import {closeRightHandSide, selectPost, setRhsExpanded, selectPostCard, selectPostFromRightHandSideSearch} from 'actions/views/rhs';
import {markPostAsUnread, emitShortcutReactToLastPostFrom} from 'actions/post_actions'; import {markPostAsUnread, emitShortcutReactToLastPostFrom} from 'actions/post_actions';
import {getShortcutReactToLastPostEmittedFrom, getOneClickReactionEmojis} from 'selectors/emojis'; import {getShortcutReactToLastPostEmittedFrom, getOneClickReactionEmojis} from 'selectors/emojis';
@@ -32,17 +33,10 @@ import {GlobalState} from 'types/store';
import {isArchivedChannel} from 'utils/channel_utils'; import {isArchivedChannel} from 'utils/channel_utils';
import {areConsecutivePostsBySameUser, canDeletePost, shouldShowActionsMenu, shouldShowDotMenu} from 'utils/post_utils'; import {areConsecutivePostsBySameUser, canDeletePost, shouldShowActionsMenu, shouldShowDotMenu} from 'utils/post_utils';
import {Locations, Preferences, RHSStates} from 'utils/constants'; import {Locations, Preferences, RHSStates} from 'utils/constants';
import {ExtendedPost, removePost} from 'mattermost-redux/actions/posts';
import {DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';
import {isThreadOpen} from 'selectors/views/threads';
import {General} from 'mattermost-redux/constants';
import {getDisplayNameByUser} from 'utils/utils'; import {getDisplayNameByUser} from 'utils/utils';
import {getDirectTeammate} from 'mattermost-redux/selectors/entities/channels';
import PostComponent from './post_component'; import PostComponent from './post_component';
import {removePostCloseRHSDeleteDraft} from './actions';
interface OwnProps { interface OwnProps {
post?: Post | UserActivityPost; post?: Post | UserActivityPost;
@@ -84,16 +78,6 @@ function isConsecutivePost(state: GlobalState, ownProps: OwnProps) {
return consecutivePost; return consecutivePost;
} }
function removePostAndCloseRHS(post: ExtendedPost) {
return (dispatch: DispatchFunc, getState: GetStateFunc) => {
const state = getState() as GlobalState;
if (isThreadOpen(state, post.id)) {
dispatch(closeRightHandSide());
}
return dispatch(removePost(post));
};
}
function makeMapStateToProps() { function makeMapStateToProps() {
const isPostCommentMention = makeIsPostCommentMention(); const isPostCommentMention = makeIsPostCommentMention();
const getReplyCount = makeGetCommentCountForPost(); const getReplyCount = makeGetCommentCountForPost();
@@ -242,7 +226,7 @@ function mapDispatchToProps(dispatch: Dispatch<AnyAction>) {
selectPost, selectPost,
selectPostFromRightHandSideSearch, selectPostFromRightHandSideSearch,
setRhsExpanded, setRhsExpanded,
removePost: removePostAndCloseRHS, removePost: removePostCloseRHSDeleteDraft,
closeRightHandSide, closeRightHandSide,
selectPostCard, selectPostCard,
}, dispatch), }, dispatch),

Просмотреть файл

@@ -621,12 +621,10 @@ const PostComponent = (props: Props): JSX.Element => {
<PostOptions <PostOptions
{...props} {...props}
teamId={teamId} teamId={teamId}
setActionsMenuInitialisationState={props.actions.setActionsMenuInitialisationState}
handleDropdownOpened={handleDropdownOpened} handleDropdownOpened={handleDropdownOpened}
handleCommentClick={handleCommentClick} handleCommentClick={handleCommentClick}
hover={hover || a11yActive} hover={hover || a11yActive}
removePost={props.actions.removePost} removePost={props.actions.removePost}
isSearchResultsItem={Boolean(isSearchResultItem)}
handleJumpClick={handleJumpClick} handleJumpClick={handleJumpClick}
isPostHeaderVisible={getPostHeaderVisible()} isPostHeaderVisible={getPostHeaderVisible()}
/> />

Просмотреть файл

@@ -29,7 +29,6 @@ type Props = {
enableEmojiPicker?: boolean; enableEmojiPicker?: boolean;
isReadOnly?: boolean; isReadOnly?: boolean;
channelIsArchived?: boolean; channelIsArchived?: boolean;
setActionsMenuInitialisationState: (initializationState: Record<string, boolean>) => void;
handleCommentClick?: (e: React.MouseEvent) => void; handleCommentClick?: (e: React.MouseEvent) => void;
handleJumpClick?: (e: React.MouseEvent) => void; handleJumpClick?: (e: React.MouseEvent) => void;
handleDropdownOpened?: (e: boolean) => void; handleDropdownOpened?: (e: boolean) => void;
@@ -42,7 +41,6 @@ type Props = {
isMobileView: boolean; isMobileView: boolean;
hasReplies?: boolean; hasReplies?: boolean;
isFirstReply?: boolean; isFirstReply?: boolean;
isSearchResultsItem?: boolean;
canReply?: boolean; canReply?: boolean;
replyCount?: number; replyCount?: number;
location: keyof typeof Locations; location: keyof typeof Locations;
@@ -85,7 +83,9 @@ const PostOptions = (props: Props): JSX.Element => {
const systemMessage = isSystemMessage(post); const systemMessage = isSystemMessage(post);
const isFromAutoResponder = fromAutoResponder(post); const isFromAutoResponder = fromAutoResponder(post);
const removePost = () => props.removePost(props.post); function removePost() {
props.removePost(props.post);
}
const toggleEmojiPicker = () => { const toggleEmojiPicker = () => {
setShowEmojiPicker(!showEmojiPicker); setShowEmojiPicker(!showEmojiPicker);

Просмотреть файл

@@ -12,7 +12,7 @@ import {getThread} from 'mattermost-redux/selectors/entities/threads';
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences'; import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {appsEnabled} from 'mattermost-redux/selectors/entities/apps'; import {appsEnabled} from 'mattermost-redux/selectors/entities/apps';
import {removePost, getNewestPostThread, getPostThread} from 'mattermost-redux/actions/posts'; import {getNewestPostThread, getPostThread} from 'mattermost-redux/actions/posts';
import {getThread as fetchThread, updateThreadRead} from 'mattermost-redux/actions/threads'; import {getThread as fetchThread, updateThreadRead} from 'mattermost-redux/actions/threads';
import {GenericAction} from 'mattermost-redux/types/actions'; import {GenericAction} from 'mattermost-redux/types/actions';
@@ -78,7 +78,6 @@ function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
getNewestPostThread, getNewestPostThread,
getPostThread, getPostThread,
getThread: fetchThread, getThread: fetchThread,
removePost,
selectPostCard, selectPostCard,
updateThreadLastOpened, updateThreadLastOpened,
updateThreadRead, updateThreadRead,

Просмотреть файл

@@ -5,7 +5,6 @@ import React, {HTMLAttributes} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import {ActionFunc} from 'mattermost-redux/types/actions'; import {ActionFunc} from 'mattermost-redux/types/actions';
import {ExtendedPost} from 'mattermost-redux/actions/posts';
import deferComponentRender from 'components/deferComponentRender'; import deferComponentRender from 'components/deferComponentRender';
import FileUploadOverlay from 'components/file_upload_overlay'; import FileUploadOverlay from 'components/file_upload_overlay';
@@ -31,7 +30,6 @@ export type Props = Attrs & {
userThread?: UserThread | null; userThread?: UserThread | null;
channel: Channel | null; channel: Channel | null;
selected: Post | FakePost; selected: Post | FakePost;
previousRhsState?: string;
currentUserId: string; currentUserId: string;
currentTeamId: string; currentTeamId: string;
socketConnectionStatus: boolean; socketConnectionStatus: boolean;
@@ -40,7 +38,6 @@ export type Props = Attrs & {
getNewestPostThread: (rootId: string) => Promise<any>|ActionFunc; getNewestPostThread: (rootId: string) => Promise<any>|ActionFunc;
getPostThread: (rootId: string, fetchThreads: boolean) => Promise<any>|ActionFunc; getPostThread: (rootId: string, fetchThreads: boolean) => Promise<any>|ActionFunc;
getThread: (userId: string, teamId: string, threadId: string, extended: boolean) => Promise<any>|ActionFunc; getThread: (userId: string, teamId: string, threadId: string, extended: boolean) => Promise<any>|ActionFunc;
removePost: (post: ExtendedPost) => void;
selectPostCard: (post: Post) => void; selectPostCard: (post: Post) => void;
updateThreadLastOpened: (threadId: string, lastViewedAt: number) => unknown; updateThreadLastOpened: (threadId: string, lastViewedAt: number) => unknown;
updateThreadRead: (userId: string, teamId: string, threadId: string, timestamp: number) => unknown; updateThreadRead: (userId: string, teamId: string, threadId: string, timestamp: number) => unknown;

Просмотреть файл

@@ -4,39 +4,39 @@
import {AnyAction} from 'redux'; import {AnyAction} from 'redux';
import {batchActions} from 'redux-batched-actions'; import {batchActions} from 'redux-batched-actions';
import {Client4, DEFAULT_LIMIT_AFTER, DEFAULT_LIMIT_BEFORE} from 'mattermost-redux/client';
import {PostTypes, ChannelTypes, FileTypes, IntegrationTypes} from 'mattermost-redux/action_types';
import {getCurrentChannelId, getMyChannelMember as getMyChannelMemberSelector} from 'mattermost-redux/selectors/entities/channels';
import {getCustomEmojisByName as selectCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import * as Selectors from 'mattermost-redux/selectors/entities/posts';
import {getCurrentUserId, getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
import {isCombinedUserActivityPost} from 'mattermost-redux/utils/post_list';
import {ActionResult, DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';
import {getUnreadScrollPositionPreference, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {General, Preferences, Posts} from '../constants';
import {UserProfile} from '@mattermost/types/users'; import {UserProfile} from '@mattermost/types/users';
import {Reaction} from '@mattermost/types/reactions'; import {Reaction} from '@mattermost/types/reactions';
import {Post, PostList, PostAcknowledgement} from '@mattermost/types/posts'; import {Post, PostList, PostAcknowledgement} from '@mattermost/types/posts';
import {GlobalState} from '@mattermost/types/store'; import {GlobalState} from '@mattermost/types/store';
import {ChannelUnread} from '@mattermost/types/channels'; import {Channel, ChannelUnread} from '@mattermost/types/channels';
import {FetchPaginatedThreadOptions} from '@mattermost/types/client4'; import {FetchPaginatedThreadOptions} from '@mattermost/types/client4';
import {getProfilesByIds, getProfilesByUsernames, getStatusesByIds} from './users'; import {Client4, DEFAULT_LIMIT_AFTER, DEFAULT_LIMIT_BEFORE} from 'mattermost-redux/client';
import {PostTypes, ChannelTypes, FileTypes, IntegrationTypes} from 'mattermost-redux/action_types';
import {ActionResult, DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';
import {getCurrentChannelId, getMyChannelMember as getMyChannelMemberSelector} from 'mattermost-redux/selectors/entities/channels';
import {getCustomEmojisByName as selectCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import * as PostSelectors from 'mattermost-redux/selectors/entities/posts';
import {getCurrentUserId, getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
import {getUnreadScrollPositionPreference, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {isCombinedUserActivityPost} from 'mattermost-redux/utils/post_list';
import {General, Preferences, Posts} from 'mattermost-redux/constants';
import {getProfilesByIds, getProfilesByUsernames, getStatusesByIds} from 'mattermost-redux/actions/users';
import { import {
deletePreferences, deletePreferences,
savePreferences, savePreferences,
} from './preferences'; } from 'mattermost-redux/actions/preferences';
import {bindClientFunc, forceLogoutIfNecessary} from './helpers'; import {bindClientFunc, forceLogoutIfNecessary} from 'mattermost-redux/actions/helpers';
import {logError} from './errors'; import {logError} from './errors';
import {systemEmojis, getCustomEmojiByName, getCustomEmojisByName} from './emojis'; import {systemEmojis, getCustomEmojiByName, getCustomEmojisByName} from 'mattermost-redux/actions/emojis';
import {selectChannel} from './channels'; import {selectChannel} from 'mattermost-redux/actions/channels';
import {decrementThreadCounts} from './threads'; import {decrementThreadCounts} from 'mattermost-redux/actions/threads';
// receivedPost should be dispatched after a single post from the server. This typically happens when an existing post // receivedPost should be dispatched after a single post from the server. This typically happens when an existing post
// is updated. // is updated.
@@ -174,7 +174,7 @@ export function createPost(post: Post, files: any[] = []) {
const pendingPostId = post.pending_post_id || `${currentUserId}:${timestamp}`; const pendingPostId = post.pending_post_id || `${currentUserId}:${timestamp}`;
let actions: AnyAction[] = []; let actions: AnyAction[] = [];
if (Selectors.isPostIdSending(state, pendingPostId)) { if (PostSelectors.isPostIdSending(state, pendingPostId)) {
return {data: true}; return {data: true};
} }
@@ -187,7 +187,7 @@ export function createPost(post: Post, files: any[] = []) {
}; };
if (post.root_id) { if (post.root_id) {
newPost.reply_count = Selectors.getPostRepliesCount(state, post.root_id) + 1; newPost.reply_count = PostSelectors.getPostRepliesCount(state, post.root_id) + 1;
} }
// We are retrying a pending post that had files // We are retrying a pending post that had files
@@ -307,7 +307,7 @@ export function createPostImmediately(post: Post, files: any[] = []) {
}; };
if (post.root_id) { if (post.root_id) {
newPost.reply_count = Selectors.getPostRepliesCount(state, post.root_id) + 1; newPost.reply_count = PostSelectors.getPostRepliesCount(state, post.root_id) + 1;
} }
if (files.length) { if (files.length) {
@@ -401,7 +401,7 @@ export function deletePost(post: ExtendedPost) {
} }
if (delPost.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && delPost.system_post_ids) { if (delPost.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && delPost.system_post_ids) {
delPost.system_post_ids.forEach((systemPostId) => { delPost.system_post_ids.forEach((systemPostId) => {
const systemPost = Selectors.getPost(state, systemPostId); const systemPost = PostSelectors.getPost(state, systemPostId);
if (systemPost) { if (systemPost) {
dispatch(deletePost(systemPost)); dispatch(deletePost(systemPost));
} }
@@ -466,7 +466,7 @@ function getUnreadPostData(unreadChan: ChannelUnread, state: GlobalState) {
export function setUnreadPost(userId: string, postId: string) { export function setUnreadPost(userId: string, postId: string) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => { return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let state = getState(); let state = getState();
const post = Selectors.getPost(state, postId); const post = PostSelectors.getPost(state, postId);
let unreadChan; let unreadChan;
try { try {
@@ -523,7 +523,7 @@ export function pinPost(postId: string) {
]; ];
const state = getState(); const state = getState();
const post = Selectors.getPost(state, postId); const post = PostSelectors.getPost(state, postId);
if (post) { if (post) {
actions.push( actions.push(
receivedPost({ receivedPost({
@@ -544,6 +544,16 @@ export function pinPost(postId: string) {
}; };
} }
/**
* Decrements the pinned post count for a channel by 1
*/
export function decrementPinnedPostCount(channelId: Channel['id']) {
return {
type: ChannelTypes.DECREMENT_PINNED_POST_COUNT,
id: channelId,
};
}
export function unpinPost(postId: string) { export function unpinPost(postId: string) {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => { return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
dispatch({type: PostTypes.EDIT_POST_REQUEST}); dispatch({type: PostTypes.EDIT_POST_REQUEST});
@@ -565,7 +575,7 @@ export function unpinPost(postId: string) {
]; ];
const state = getState(); const state = getState();
const post = Selectors.getPost(state, postId); const post = PostSelectors.getPost(state, postId);
if (post) { if (post) {
actions.push( actions.push(
receivedPost({ receivedPost({
@@ -573,10 +583,7 @@ export function unpinPost(postId: string) {
is_pinned: false, is_pinned: false,
update_at: Date.now(), update_at: Date.now(),
}, isCollapsedThreadsEnabled(state)), }, isCollapsedThreadsEnabled(state)),
{ decrementPinnedPostCount(post.channel_id),
type: ChannelTypes.DECREMENT_PINNED_POST_COUNT,
id: post.channel_id,
},
); );
} }
@@ -781,7 +788,7 @@ export function getPostThread(rootId: string, fetchThreads = true) {
} }
export function getNewestPostThread(rootId: string) { export function getNewestPostThread(rootId: string) {
const getPostsForThread = Selectors.makeGetPostsForThread(); const getPostsForThread = PostSelectors.makeGetPostsForThread();
return async (dispatch: DispatchFunc, getState: GetStateFunc) => { return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
dispatch({type: PostTypes.GET_POST_THREAD_REQUEST}); dispatch({type: PostTypes.GET_POST_THREAD_REQUEST});
@@ -1016,7 +1023,7 @@ export function getThreadsForPosts(posts: Post[], fetchThreads = true) {
if (!post.root_id) { if (!post.root_id) {
return; return;
} }
const rootPost = Selectors.getPost(state, post.root_id); const rootPost = PostSelectors.getPost(state, post.root_id);
if (!rootPost) { if (!rootPost) {
rootsSet.add(post.root_id); rootsSet.add(post.root_id);
@@ -1200,7 +1207,7 @@ export function removePost(post: ExtendedPost) {
if (post.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && post.system_post_ids) { if (post.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && post.system_post_ids) {
const state = getState(); const state = getState();
for (const systemPostId of post.system_post_ids) { for (const systemPostId of post.system_post_ids) {
const systemPost = Selectors.getPost(state, systemPostId); const systemPost = PostSelectors.getPost(state, systemPostId);
if (systemPost) { if (systemPost) {
dispatch(removePost(systemPost as any) as any); dispatch(removePost(systemPost as any) as any);
@@ -1209,12 +1216,7 @@ export function removePost(post: ExtendedPost) {
} else { } else {
dispatch(postRemoved(post)); dispatch(postRemoved(post));
if (post.is_pinned) { if (post.is_pinned) {
dispatch( dispatch(decrementPinnedPostCount(post.channel_id));
{
type: ChannelTypes.DECREMENT_PINNED_POST_COUNT,
id: post.channel_id,
},
);
} }
} }
return {data: true}; return {data: true};