mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-59881 Bubble submit result and expose to plugins (#27766)
Automatic Merge
This commit is contained in:
parent
ed2d838ac7
commit
6027c850bd
@ -183,7 +183,7 @@ describe('executeCommand', () => {
|
|||||||
modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL,
|
modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ describe('executeCommand', () => {
|
|||||||
modalId: 'user_settings',
|
modalId: 'user_settings',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ describe('executeCommand', () => {
|
|||||||
toHaveBeenCalledWith('/leave is not supported in reply threads. Use it in the center channel instead.',
|
toHaveBeenCalledWith('/leave is not supported in reply threads. Use it in the center channel instead.',
|
||||||
'channel_id', 'root_id');
|
'channel_id', 'root_id');
|
||||||
|
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show private modal if channel is private', async () => {
|
test('should show private modal if channel is private', async () => {
|
||||||
@ -236,7 +236,7 @@ describe('executeCommand', () => {
|
|||||||
dialogProps: {channel: {type: Constants.PRIVATE_CHANNEL}},
|
dialogProps: {channel: {type: Constants.PRIVATE_CHANNEL}},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should use user id as name if channel is dm', async () => {
|
test('should use user id as name if channel is dm', async () => {
|
||||||
@ -248,7 +248,7 @@ describe('executeCommand', () => {
|
|||||||
const result = await store.dispatch(executeCommand('/leave', {}));
|
const result = await store.dispatch(executeCommand('/leave', {}));
|
||||||
expect(store.getActions()[0].data).toEqual([{category: 'direct_channel_show', name: 'userId', user_id: 'user123', value: 'false'}]);
|
expect(store.getActions()[0].data).toEqual([{category: 'direct_channel_show', name: 'userId', user_id: 'user123', value: 'false'}]);
|
||||||
|
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should use channel id as name if channel is gm', async () => {
|
test('should use channel id as name if channel is gm', async () => {
|
||||||
@ -260,7 +260,7 @@ describe('executeCommand', () => {
|
|||||||
const result = await store.dispatch(executeCommand('/leave', {}));
|
const result = await store.dispatch(executeCommand('/leave', {}));
|
||||||
expect(store.getActions()[0].data).toEqual([{category: 'group_channel_show', name: 'channelId', user_id: 'user123', value: 'false'}]);
|
expect(store.getActions()[0].data).toEqual([{category: 'group_channel_show', name: 'channelId', user_id: 'user123', value: 'false'}]);
|
||||||
|
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ describe('executeCommand', () => {
|
|||||||
type: ActionTypes.MODAL_OPEN,
|
type: ActionTypes.MODAL_OPEN,
|
||||||
modalId: ModalIdentifiers.PLUGIN_MARKETPLACE,
|
modalId: ModalIdentifiers.PLUGIN_MARKETPLACE,
|
||||||
});
|
});
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show error when marketpace is not enabled', async () => {
|
test('should show error when marketpace is not enabled', async () => {
|
||||||
@ -395,7 +395,7 @@ describe('executeCommand', () => {
|
|||||||
query: undefined,
|
query: undefined,
|
||||||
selected_field: undefined,
|
selected_field: undefined,
|
||||||
}, true);
|
}, true);
|
||||||
expect(result).toEqual({data: true});
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// 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 type {CommandArgs} from '@mattermost/types/integrations';
|
import type {AppCallResponse} from '@mattermost/types/apps';
|
||||||
|
import type {CommandArgs, CommandResponse} from '@mattermost/types/integrations';
|
||||||
|
|
||||||
import {IntegrationTypes} from 'mattermost-redux/action_types';
|
import {IntegrationTypes} from 'mattermost-redux/action_types';
|
||||||
import {unfavoriteChannel} from 'mattermost-redux/actions/channels';
|
import {unfavoriteChannel} from 'mattermost-redux/actions/channels';
|
||||||
@ -39,7 +40,14 @@ import type {GlobalState} from 'types/store';
|
|||||||
import {doAppSubmit, openAppsModal, postEphemeralCallResponseForCommandArgs} from './apps';
|
import {doAppSubmit, openAppsModal, postEphemeralCallResponseForCommandArgs} from './apps';
|
||||||
import {trackEvent} from './telemetry_actions';
|
import {trackEvent} from './telemetry_actions';
|
||||||
|
|
||||||
export function executeCommand(message: string, args: CommandArgs): ActionFuncAsync<boolean, GlobalState> {
|
export type ExecuteCommandReturnType = {
|
||||||
|
frontendHandled?: boolean;
|
||||||
|
silentFailureReason?: Error;
|
||||||
|
commandResponse?: CommandResponse;
|
||||||
|
appResponse?: AppCallResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executeCommand(message: string, args: CommandArgs): ActionFuncAsync<ExecuteCommandReturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState() as GlobalState;
|
const state = getState() as GlobalState;
|
||||||
|
|
||||||
@ -71,7 +79,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
|
|||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case '/search':
|
case '/search':
|
||||||
dispatch(PostActions.searchForTerm(msg.substring(cmdLength + 1, msg.length)));
|
dispatch(PostActions.searchForTerm(msg.substring(cmdLength + 1, msg.length)));
|
||||||
return {data: true};
|
return {data: {frontendHandled: true}};
|
||||||
case '/shortcuts':
|
case '/shortcuts':
|
||||||
if (UserAgent.isMobile()) {
|
if (UserAgent.isMobile()) {
|
||||||
const error = {message: localizeMessage('create_post.shortcutsNotSupported', 'Keyboard shortcuts are not supported on your device')};
|
const error = {message: localizeMessage('create_post.shortcutsNotSupported', 'Keyboard shortcuts are not supported on your device')};
|
||||||
@ -79,20 +87,20 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(openModal({modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL, dialogType: KeyboardShortcutsModal}));
|
dispatch(openModal({modalId: ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL, dialogType: KeyboardShortcutsModal}));
|
||||||
return {data: true};
|
return {data: {frontendHandled: true}};
|
||||||
case '/leave': {
|
case '/leave': {
|
||||||
// /leave command not supported in reply threads.
|
// /leave command not supported in reply threads.
|
||||||
if (args.channel_id && args.root_id) {
|
if (args.channel_id && args.root_id) {
|
||||||
dispatch(GlobalActions.sendEphemeralPost('/leave is not supported in reply threads. Use it in the center channel instead.', args.channel_id, args.root_id));
|
dispatch(GlobalActions.sendEphemeralPost('/leave is not supported in reply threads. Use it in the center channel instead.', args.channel_id, args.root_id));
|
||||||
return {data: true};
|
return {data: {frontendHandled: true}};
|
||||||
}
|
}
|
||||||
const channel = getCurrentChannel(state);
|
const channel = getCurrentChannel(state);
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
return {data: false};
|
return {data: {silentFailureReason: new Error('cannot find current channel')}};
|
||||||
}
|
}
|
||||||
if (channel.type === Constants.PRIVATE_CHANNEL) {
|
if (channel.type === Constants.PRIVATE_CHANNEL) {
|
||||||
dispatch(openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}}));
|
dispatch(openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}}));
|
||||||
return {data: true};
|
return {data: {frontendHandled: true}};
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
channel.type === Constants.DM_CHANNEL ||
|
channel.type === Constants.DM_CHANNEL ||
|
||||||
@ -118,13 +126,13 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
|
|||||||
dispatch(unfavoriteChannel(channel.id));
|
dispatch(unfavoriteChannel(channel.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {data: true};
|
return {data: {frontendHandled: true}};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case '/settings':
|
case '/settings':
|
||||||
dispatch(openModal({modalId: ModalIdentifiers.USER_SETTINGS, dialogType: UserSettingsModal, dialogProps: {isContentProductSettings: true}}));
|
dispatch(openModal({modalId: ModalIdentifiers.USER_SETTINGS, dialogType: UserSettingsModal, dialogProps: {isContentProductSettings: true}}));
|
||||||
return {data: true};
|
return {data: {frontendHandled: true}};
|
||||||
case '/marketplace':
|
case '/marketplace':
|
||||||
// check if user has permissions to access the read plugins
|
// check if user has permissions to access the read plugins
|
||||||
if (!haveICurrentTeamPermission(state, Permissions.SYSCONSOLE_WRITE_PLUGINS)) {
|
if (!haveICurrentTeamPermission(state, Permissions.SYSCONSOLE_WRITE_PLUGINS)) {
|
||||||
@ -137,7 +145,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(openModal({modalId: ModalIdentifiers.PLUGIN_MARKETPLACE, dialogType: MarketplaceModal, dialogProps: {openedFrom: 'command'}}));
|
dispatch(openModal({modalId: ModalIdentifiers.PLUGIN_MARKETPLACE, dialogType: MarketplaceModal, dialogProps: {openedFrom: 'command'}}));
|
||||||
return {data: true};
|
return {data: {frontendHandled: true}};
|
||||||
case '/collapse':
|
case '/collapse':
|
||||||
case '/expand':
|
case '/expand':
|
||||||
dispatch(PostActions.resetEmbedVisibility());
|
dispatch(PostActions.resetEmbedVisibility());
|
||||||
@ -173,14 +181,14 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
|
|||||||
if (callResp.text) {
|
if (callResp.text) {
|
||||||
dispatch(postEphemeralCallResponseForCommandArgs(callResp, callResp.text, args));
|
dispatch(postEphemeralCallResponseForCommandArgs(callResp, callResp.text, args));
|
||||||
}
|
}
|
||||||
return {data: true};
|
return {data: {appResponse: callResp}};
|
||||||
case AppCallResponseTypes.FORM:
|
case AppCallResponseTypes.FORM:
|
||||||
if (callResp.form) {
|
if (callResp.form) {
|
||||||
dispatch(openAppsModal(callResp.form, creq.context));
|
dispatch(openAppsModal(callResp.form, creq.context));
|
||||||
}
|
}
|
||||||
return {data: true};
|
return {data: {appResponse: callResp}};
|
||||||
case AppCallResponseTypes.NAVIGATE:
|
case AppCallResponseTypes.NAVIGATE:
|
||||||
return {data: true};
|
return {data: {appResponse: callResp}};
|
||||||
default:
|
default:
|
||||||
return createErrorMessage(intlShim.formatMessage(
|
return createErrorMessage(intlShim.formatMessage(
|
||||||
{
|
{
|
||||||
@ -213,7 +221,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
|
|||||||
|
|
||||||
if (msg.trim() === '/logout') {
|
if (msg.trim() === '/logout') {
|
||||||
GlobalActions.emitUserLoggedOutEvent(hasGotoLocation ? data.goto_location : '/');
|
GlobalActions.emitUserLoggedOutEvent(hasGotoLocation ? data.goto_location : '/');
|
||||||
return {data: true};
|
return {data: {response: data}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.trigger_id) {
|
if (data.trigger_id) {
|
||||||
@ -230,6 +238,6 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {data: true};
|
return {data: {response: data}};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -358,7 +358,7 @@ describe('Actions.Posts', () => {
|
|||||||
|
|
||||||
const immediateExpectedState = [{
|
const immediateExpectedState = [{
|
||||||
args: [newPost, files],
|
args: [newPost, files],
|
||||||
type: 'MOCK_CREATE_POST_IMMEDIATELY',
|
type: 'MOCK_CREATE_POST',
|
||||||
}, {
|
}, {
|
||||||
args: ['draft_current_channel_id', null],
|
args: ['draft_current_channel_id', null],
|
||||||
type: 'MOCK_SET_GLOBAL_ITEM',
|
type: 'MOCK_SET_GLOBAL_ITEM',
|
||||||
@ -453,8 +453,8 @@ describe('Actions.Posts', () => {
|
|||||||
testStore.dispatch(Actions.submitReaction('post_id_1', '+', 'emoji_name_1'));
|
testStore.dispatch(Actions.submitReaction('post_id_1', '+', 'emoji_name_1'));
|
||||||
|
|
||||||
expect(testStore.getActions()).toEqual([
|
expect(testStore.getActions()).toEqual([
|
||||||
{args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'},
|
|
||||||
{args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'},
|
{args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'},
|
||||||
|
{args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -503,8 +503,8 @@ describe('Actions.Posts', () => {
|
|||||||
testStore.dispatch(Actions.toggleReaction('post_id_1', 'emoji_name_1'));
|
testStore.dispatch(Actions.toggleReaction('post_id_1', 'emoji_name_1'));
|
||||||
|
|
||||||
expect(testStore.getActions()).toEqual([
|
expect(testStore.getActions()).toEqual([
|
||||||
{args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'},
|
|
||||||
{args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'},
|
{args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'},
|
||||||
|
{args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -529,8 +529,8 @@ describe('Actions.Posts', () => {
|
|||||||
|
|
||||||
await testStore.dispatch(Actions.addReaction('post_id_1', 'emoji_name_1'));
|
await testStore.dispatch(Actions.addReaction('post_id_1', 'emoji_name_1'));
|
||||||
expect(testStore.getActions()).toEqual([
|
expect(testStore.getActions()).toEqual([
|
||||||
{args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'},
|
|
||||||
{args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'},
|
{args: ['emoji_name_1'], type: 'MOCK_ADD_RECENT_EMOJI'},
|
||||||
|
{args: ['post_id_1', 'emoji_name_1'], type: 'MOCK_ADD_REACTION'},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
test('should not add reaction if we are over the limit', async () => {
|
test('should not add reaction if we are over the limit', async () => {
|
||||||
|
@ -40,12 +40,12 @@ import {
|
|||||||
} from 'utils/constants';
|
} from 'utils/constants';
|
||||||
import {matchEmoticons} from 'utils/emoticons';
|
import {matchEmoticons} from 'utils/emoticons';
|
||||||
import {makeGetIsReactionAlreadyAddedToPost, makeGetUniqueEmojiNameReactionsForPost} from 'utils/post_utils';
|
import {makeGetIsReactionAlreadyAddedToPost, makeGetUniqueEmojiNameReactionsForPost} from 'utils/post_utils';
|
||||||
import * as UserAgent from 'utils/user_agent';
|
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
import {completePostReceive} from './new_post';
|
import {completePostReceive} from './new_post';
|
||||||
import type {NewPostMessageProps} from './new_post';
|
import type {NewPostMessageProps} from './new_post';
|
||||||
|
import type {SubmitPostReturnType} from './views/create_comment';
|
||||||
|
|
||||||
export function handleNewPost(post: Post, msg?: {data?: NewPostMessageProps & GroupChannel}): ActionFuncAsync<boolean, GlobalState> {
|
export function handleNewPost(post: Post, msg?: {data?: NewPostMessageProps & GroupChannel}): ActionFuncAsync<boolean, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
@ -106,7 +106,7 @@ export function unflagPost(postId: string): ActionFuncAsync {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPost(post: Post, files: FileInfo[]): ActionFuncAsync {
|
export function createPost(post: Post, files: FileInfo[], afterSubmit?: (response: SubmitPostReturnType) => void): ActionFuncAsync<PostActions.CreatePostReturnType, GlobalState> {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
// parse message and emit emoji event
|
// parse message and emit emoji event
|
||||||
const emojis = matchEmoticons(post.message);
|
const emojis = matchEmoticons(post.message);
|
||||||
@ -115,12 +115,7 @@ export function createPost(post: Post, files: FileInfo[]): ActionFuncAsync {
|
|||||||
dispatch(addRecentEmojis(trimmedEmojis));
|
dispatch(addRecentEmojis(trimmedEmojis));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result;
|
const result = await dispatch(PostActions.createPost(post, files, afterSubmit));
|
||||||
if (UserAgent.isIosClassic()) {
|
|
||||||
result = await dispatch(PostActions.createPostImmediately(post, files));
|
|
||||||
} else {
|
|
||||||
result = await dispatch(PostActions.createPost(post, files));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (post.root_id) {
|
if (post.root_id) {
|
||||||
dispatch(storeCommentDraft(post.root_id, null));
|
dispatch(storeCommentDraft(post.root_id, null));
|
||||||
@ -146,23 +141,23 @@ function storeCommentDraft(rootPostId: string, draft: null): ActionFunc {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitReaction(postId: string, action: string, emojiName: string): ActionFunc<unknown, GlobalState> {
|
export function submitReaction(postId: string, action: string, emojiName: string): ActionFuncAsync<PostActions.SubmitReactionReturnType, GlobalState> {
|
||||||
return (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState() as GlobalState;
|
const state = getState() as GlobalState;
|
||||||
const getIsReactionAlreadyAddedToPost = makeGetIsReactionAlreadyAddedToPost();
|
const getIsReactionAlreadyAddedToPost = makeGetIsReactionAlreadyAddedToPost();
|
||||||
|
|
||||||
const isReactionAlreadyAddedToPost = getIsReactionAlreadyAddedToPost(state, postId, emojiName);
|
const isReactionAlreadyAddedToPost = getIsReactionAlreadyAddedToPost(state, postId, emojiName);
|
||||||
|
|
||||||
if (action === '+' && !isReactionAlreadyAddedToPost) {
|
if (action === '+' && !isReactionAlreadyAddedToPost) {
|
||||||
dispatch(addReaction(postId, emojiName));
|
return dispatch(addReaction(postId, emojiName));
|
||||||
} else if (action === '-' && isReactionAlreadyAddedToPost) {
|
} else if (action === '-' && isReactionAlreadyAddedToPost) {
|
||||||
dispatch(PostActions.removeReaction(postId, emojiName));
|
return dispatch(PostActions.removeReaction(postId, emojiName));
|
||||||
}
|
}
|
||||||
return {data: true};
|
return {error: new Error(`unknown action ${action}`)};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleReaction(postId: string, emojiName: string): ActionFuncAsync<unknown, GlobalState> {
|
export function toggleReaction(postId: string, emojiName: string): ActionFuncAsync<PostActions.SubmitReactionReturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const getIsReactionAlreadyAddedToPost = makeGetIsReactionAlreadyAddedToPost();
|
const getIsReactionAlreadyAddedToPost = makeGetIsReactionAlreadyAddedToPost();
|
||||||
@ -176,9 +171,9 @@ export function toggleReaction(postId: string, emojiName: string): ActionFuncAsy
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addReaction(postId: string, emojiName: string): ActionFunc {
|
export function addReaction(postId: string, emojiName: string): ActionFuncAsync<PostActions.SubmitReactionReturnType, GlobalState> {
|
||||||
const getUniqueEmojiNameReactionsForPost = makeGetUniqueEmojiNameReactionsForPost();
|
const getUniqueEmojiNameReactionsForPost = makeGetUniqueEmojiNameReactionsForPost();
|
||||||
return (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState() as GlobalState;
|
const state = getState() as GlobalState;
|
||||||
const config = getConfig(state);
|
const config = getConfig(state);
|
||||||
const uniqueEmojiNames = getUniqueEmojiNameReactionsForPost(state, postId) ?? [];
|
const uniqueEmojiNames = getUniqueEmojiNameReactionsForPost(state, postId) ?? [];
|
||||||
@ -193,12 +188,12 @@ export function addReaction(postId: string, emojiName: string): ActionFunc {
|
|||||||
onExited: () => closeModal(ModalIdentifiers.REACTION_LIMIT_REACHED),
|
onExited: () => closeModal(ModalIdentifiers.REACTION_LIMIT_REACHED),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
return {data: false};
|
return {error: new Error('reached reaction limit')};
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(PostActions.addReaction(postId, emojiName));
|
|
||||||
dispatch(addRecentEmoji(emojiName));
|
dispatch(addRecentEmoji(emojiName));
|
||||||
return {data: true};
|
const result = await dispatch(PostActions.addReaction(postId, emojiName));
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
|
|
||||||
import type {Post} from '@mattermost/types/posts';
|
import type {Post} from '@mattermost/types/posts';
|
||||||
|
|
||||||
import {
|
import type {CreatePostReturnType, SubmitReactionReturnType} from 'mattermost-redux/actions/posts';
|
||||||
addMessageIntoHistory,
|
import {addMessageIntoHistory} from 'mattermost-redux/actions/posts';
|
||||||
} from 'mattermost-redux/actions/posts';
|
|
||||||
import {Permissions} from 'mattermost-redux/constants';
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||||
@ -25,6 +24,7 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
|||||||
import type {ActionFunc, ActionFuncAsync} from 'mattermost-redux/types/actions';
|
import type {ActionFunc, ActionFuncAsync} from 'mattermost-redux/types/actions';
|
||||||
import {isPostPendingOrFailed} from 'mattermost-redux/utils/post_utils';
|
import {isPostPendingOrFailed} from 'mattermost-redux/utils/post_utils';
|
||||||
|
|
||||||
|
import type {ExecuteCommandReturnType} from 'actions/command';
|
||||||
import {executeCommand} from 'actions/command';
|
import {executeCommand} from 'actions/command';
|
||||||
import {runMessageWillBePostedHooks, runSlashCommandWillBePostedHooks} from 'actions/hooks';
|
import {runMessageWillBePostedHooks, runSlashCommandWillBePostedHooks} from 'actions/hooks';
|
||||||
import * as PostActions from 'actions/post_actions';
|
import * as PostActions from 'actions/post_actions';
|
||||||
@ -56,7 +56,7 @@ export function updateCommentDraft(rootId: string, draft?: PostDraft, save = fal
|
|||||||
return updateDraft(key, draft ?? null, rootId, save);
|
return updateDraft(key, draft ?? null, rootId, save);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitPost(channelId: string, rootId: string, draft: PostDraft): ActionFuncAsync {
|
export function submitPost(channelId: string, rootId: string, draft: PostDraft, afterSubmit?: (response: SubmitPostReturnType) => void): ActionFuncAsync<CreatePostReturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
@ -103,11 +103,13 @@ export function submitPost(channelId: string, rootId: string, draft: PostDraft):
|
|||||||
|
|
||||||
post = hookResult.data;
|
post = hookResult.data;
|
||||||
|
|
||||||
return dispatch(PostActions.createPost(post, draft.fileInfos));
|
return dispatch(PostActions.createPost(post, draft.fileInfos, afterSubmit));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitCommand(channelId: string, rootId: string, draft: PostDraft): ActionFuncAsync<unknown, GlobalState> {
|
type SubmitCommandRerturnType = ExecuteCommandReturnType & CreatePostReturnType;
|
||||||
|
|
||||||
|
export function submitCommand(channelId: string, rootId: string, draft: PostDraft): ActionFuncAsync<SubmitCommandRerturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
@ -126,13 +128,13 @@ export function submitCommand(channelId: string, rootId: string, draft: PostDraf
|
|||||||
return {error: hookResult.error};
|
return {error: hookResult.error};
|
||||||
} else if (!hookResult.data!.message && !hookResult.data!.args) {
|
} else if (!hookResult.data!.message && !hookResult.data!.args) {
|
||||||
// do nothing with an empty return from a hook
|
// do nothing with an empty return from a hook
|
||||||
return {};
|
return {error: new Error('command not submitted due to plugin hook')};
|
||||||
}
|
}
|
||||||
|
|
||||||
message = hookResult.data!.message;
|
message = hookResult.data!.message;
|
||||||
args = hookResult.data!.args;
|
args = hookResult.data!.args;
|
||||||
|
|
||||||
const {error} = await dispatch(executeCommand(message, args));
|
const {error, data} = await dispatch(executeCommand(message, args));
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error.sendMessage) {
|
if (error.sendMessage) {
|
||||||
@ -141,7 +143,7 @@ export function submitCommand(channelId: string, rootId: string, draft: PostDraf
|
|||||||
throw (error);
|
throw (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {data: data!};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +177,9 @@ export function makeOnSubmit(channelId: string, rootId: string, latestPostId: st
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onSubmit(draft: PostDraft, options: {ignoreSlash?: boolean}): ActionFuncAsync<boolean, GlobalState> {
|
export type SubmitPostReturnType = CreatePostReturnType & SubmitCommandRerturnType & SubmitReactionReturnType;
|
||||||
|
|
||||||
|
export function onSubmit(draft: PostDraft, options: {ignoreSlash?: boolean; afterSubmit?: (response: SubmitPostReturnType) => void}): ActionFuncAsync<SubmitPostReturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const {message, channelId, rootId} = draft;
|
const {message, channelId, rootId} = draft;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -190,14 +194,16 @@ export function onSubmit(draft: PostDraft, options: {ignoreSlash?: boolean}): Ac
|
|||||||
if (isReaction && emojiMap.has(isReaction[2])) {
|
if (isReaction && emojiMap.has(isReaction[2])) {
|
||||||
const latestPostId = getLatestInteractablePostId(state, channelId, rootId);
|
const latestPostId = getLatestInteractablePostId(state, channelId, rootId);
|
||||||
if (latestPostId) {
|
if (latestPostId) {
|
||||||
dispatch(PostActions.submitReaction(latestPostId, isReaction[1], isReaction[2]));
|
return dispatch(PostActions.submitReaction(latestPostId, isReaction[1], isReaction[2]));
|
||||||
}
|
}
|
||||||
} else if (message.indexOf('/') === 0 && !options.ignoreSlash) {
|
return {error: new Error('no post to react to')};
|
||||||
await dispatch(submitCommand(channelId, rootId, draft));
|
|
||||||
} else {
|
|
||||||
await dispatch(submitPost(channelId, rootId, draft));
|
|
||||||
}
|
}
|
||||||
return {data: true};
|
|
||||||
|
if (message.indexOf('/') === 0 && !options.ignoreSlash) {
|
||||||
|
return dispatch(submitCommand(channelId, rootId, draft));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispatch(submitPost(channelId, rootId, draft, options.afterSubmit));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import type {SubmitPostReturnType} from 'actions/views/create_comment';
|
||||||
|
|
||||||
import AdvancedTextEditor from 'components/advanced_text_editor/advanced_text_editor';
|
import AdvancedTextEditor from 'components/advanced_text_editor/advanced_text_editor';
|
||||||
|
|
||||||
import {Locations} from 'utils/constants';
|
import {Locations} from 'utils/constants';
|
||||||
@ -19,6 +21,11 @@ export type Props = {
|
|||||||
|
|
||||||
isThreadView?: boolean;
|
isThreadView?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by plugins to act after the post is made
|
||||||
|
*/
|
||||||
|
afterSubmit?: (response: SubmitPostReturnType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AdvancedCreateComment = ({
|
const AdvancedCreateComment = ({
|
||||||
@ -26,6 +33,7 @@ const AdvancedCreateComment = ({
|
|||||||
rootId,
|
rootId,
|
||||||
isThreadView,
|
isThreadView,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
afterSubmit,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<AdvancedTextEditor
|
<AdvancedTextEditor
|
||||||
@ -34,6 +42,7 @@ const AdvancedCreateComment = ({
|
|||||||
postId={rootId}
|
postId={rootId}
|
||||||
isThreadView={isThreadView}
|
isThreadView={isThreadView}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
afterSubmit={afterSubmit}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,7 @@ import {getCurrentUserId, isCurrentUserGuestUser, getStatusForUserId, makeGetDis
|
|||||||
|
|
||||||
import * as GlobalActions from 'actions/global_actions';
|
import * as GlobalActions from 'actions/global_actions';
|
||||||
import {actionOnGlobalItemsWithPrefix} from 'actions/storage';
|
import {actionOnGlobalItemsWithPrefix} from 'actions/storage';
|
||||||
|
import type {SubmitPostReturnType} from 'actions/views/create_comment';
|
||||||
import {removeDraft, updateDraft} from 'actions/views/drafts';
|
import {removeDraft, updateDraft} from 'actions/views/drafts';
|
||||||
import {makeGetDraft} from 'selectors/rhs';
|
import {makeGetDraft} from 'selectors/rhs';
|
||||||
import {connectionErrorCount} from 'selectors/views/system';
|
import {connectionErrorCount} from 'selectors/views/system';
|
||||||
@ -79,14 +80,20 @@ type Props = {
|
|||||||
postId: string;
|
postId: string;
|
||||||
isThreadView?: boolean;
|
isThreadView?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by plugins to act after the post is made
|
||||||
|
*/
|
||||||
|
afterSubmit?: (response: SubmitPostReturnType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AdvanceTextEditor = ({
|
const AdvancedTextEditor = ({
|
||||||
location,
|
location,
|
||||||
channelId,
|
channelId,
|
||||||
postId,
|
postId,
|
||||||
isThreadView = false,
|
isThreadView = false,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
afterSubmit,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
|
|
||||||
@ -244,7 +251,7 @@ const AdvanceTextEditor = ({
|
|||||||
isValidPersistentNotifications,
|
isValidPersistentNotifications,
|
||||||
onSubmitCheck: prioritySubmitCheck,
|
onSubmitCheck: prioritySubmitCheck,
|
||||||
} = usePriority(draft, handleDraftChange, focusTextbox, showPreview);
|
} = usePriority(draft, handleDraftChange, focusTextbox, showPreview);
|
||||||
const [handleSubmit, errorClass] = useSubmit(draft, postError, channelId, postId, serverError, lastBlurAt, focusTextbox, setServerError, setPostError, setShowPreview, handleDraftChange, prioritySubmitCheck);
|
const [handleSubmit, errorClass] = useSubmit(draft, postError, channelId, postId, serverError, lastBlurAt, focusTextbox, setServerError, setPostError, setShowPreview, handleDraftChange, prioritySubmitCheck, afterSubmit);
|
||||||
const [handleKeyDown, postMsgKeyPress] = useKeyHandler(
|
const [handleKeyDown, postMsgKeyPress] = useKeyHandler(
|
||||||
draft,
|
draft,
|
||||||
channelId,
|
channelId,
|
||||||
@ -660,4 +667,4 @@ const AdvanceTextEditor = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdvanceTextEditor;
|
export default AdvancedTextEditor;
|
||||||
|
@ -16,6 +16,7 @@ import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'
|
|||||||
import {getCurrentUserId, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
import {scrollPostListToBottom} from 'actions/views/channel';
|
import {scrollPostListToBottom} from 'actions/views/channel';
|
||||||
|
import type {SubmitPostReturnType} from 'actions/views/create_comment';
|
||||||
import {onSubmit} from 'actions/views/create_comment';
|
import {onSubmit} from 'actions/views/create_comment';
|
||||||
import {openModal} from 'actions/views/modals';
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ const useSubmit = (
|
|||||||
setShowPreview: (showPreview: boolean) => void,
|
setShowPreview: (showPreview: boolean) => void,
|
||||||
handleDraftChange: (draft: PostDraft, options?: {instant?: boolean; show?: boolean}) => void,
|
handleDraftChange: (draft: PostDraft, options?: {instant?: boolean; show?: boolean}) => void,
|
||||||
prioritySubmitCheck: (onConfirm: () => void) => boolean,
|
prioritySubmitCheck: (onConfirm: () => void) => boolean,
|
||||||
|
afterSubmit?: (response: SubmitPostReturnType) => void,
|
||||||
): [
|
): [
|
||||||
(e: React.FormEvent, submittingDraft?: PostDraft) => void,
|
(e: React.FormEvent, submittingDraft?: PostDraft) => void,
|
||||||
string | null,
|
string | null,
|
||||||
@ -153,7 +155,7 @@ const useSubmit = (
|
|||||||
setServerError(null);
|
setServerError(null);
|
||||||
|
|
||||||
const ignoreSlash = isErrorInvalidSlashCommand(serverError) && serverError?.submittedMessage === submittingDraft.message;
|
const ignoreSlash = isErrorInvalidSlashCommand(serverError) && serverError?.submittedMessage === submittingDraft.message;
|
||||||
const options = {ignoreSlash};
|
const options = {ignoreSlash, afterSubmit};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await dispatch(onSubmit(submittingDraft, options));
|
await dispatch(onSubmit(submittingDraft, options));
|
||||||
@ -188,7 +190,7 @@ const useSubmit = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDraftSubmitting.current = false;
|
isDraftSubmitting.current = false;
|
||||||
}, [handleDraftChange, dispatch, draft, focusTextbox, isRootDeleted, postError, serverError, showPostDeletedModal, channelId, postId, lastBlurAt, setPostError, setServerError]);
|
}, [handleDraftChange, dispatch, draft, focusTextbox, isRootDeleted, postError, serverError, showPostDeletedModal, channelId, postId, lastBlurAt, setPostError, setServerError, afterSubmit]);
|
||||||
|
|
||||||
const showNotifyAllModal = useCallback((mentions: string[], channelTimezoneCount: number, memberNotifyCount: number) => {
|
const showNotifyAllModal = useCallback((mentions: string[], channelTimezoneCount: number, memberNotifyCount: number) => {
|
||||||
dispatch(openModal({
|
dispatch(openModal({
|
||||||
|
@ -89,38 +89,6 @@ describe('Actions.Posts', () => {
|
|||||||
expect(!postsInChannel[channelId]).toBeTruthy();
|
expect(!postsInChannel[channelId]).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('maintain postReplies', async () => {
|
|
||||||
const channelId = TestHelper.basicChannel!.id;
|
|
||||||
const post = TestHelper.fakePost(channelId);
|
|
||||||
const postId = TestHelper.generateId();
|
|
||||||
|
|
||||||
nock(Client4.getBaseRoute()).
|
|
||||||
post('/posts').
|
|
||||||
reply(201, {...post, id: postId});
|
|
||||||
|
|
||||||
await store.dispatch(Actions.createPostImmediately(post));
|
|
||||||
|
|
||||||
const post2 = TestHelper.fakePostWithId(channelId);
|
|
||||||
post2.root_id = postId;
|
|
||||||
|
|
||||||
nock(Client4.getBaseRoute()).
|
|
||||||
post('/posts').
|
|
||||||
reply(201, post2);
|
|
||||||
|
|
||||||
await store.dispatch(Actions.createPostImmediately(post2));
|
|
||||||
|
|
||||||
expect(store.getState().entities.posts.postsReplies[postId]).toBe(1);
|
|
||||||
|
|
||||||
nock(Client4.getBaseRoute()).
|
|
||||||
delete(`/posts/${post2.id}`).
|
|
||||||
reply(200, OK_RESPONSE);
|
|
||||||
|
|
||||||
await store.dispatch(Actions.deletePost(post2));
|
|
||||||
await store.dispatch(Actions.removePost(post2));
|
|
||||||
|
|
||||||
expect(store.getState().entities.posts.postsReplies[postId]).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('resetCreatePostRequest', async () => {
|
it('resetCreatePostRequest', async () => {
|
||||||
const channelId = TestHelper.basicChannel!.id;
|
const channelId = TestHelper.basicChannel!.id;
|
||||||
const post = TestHelper.fakePost(channelId);
|
const post = TestHelper.fakePost(channelId);
|
||||||
|
@ -8,6 +8,7 @@ import type {Channel, ChannelUnread} from '@mattermost/types/channels';
|
|||||||
import type {FetchPaginatedThreadOptions} from '@mattermost/types/client4';
|
import type {FetchPaginatedThreadOptions} from '@mattermost/types/client4';
|
||||||
import type {Group} from '@mattermost/types/groups';
|
import type {Group} from '@mattermost/types/groups';
|
||||||
import type {Post, PostList, PostAcknowledgement} from '@mattermost/types/posts';
|
import type {Post, PostList, PostAcknowledgement} from '@mattermost/types/posts';
|
||||||
|
import type {Reaction} from '@mattermost/types/reactions';
|
||||||
import type {GlobalState} from '@mattermost/types/store';
|
import type {GlobalState} from '@mattermost/types/store';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
@ -168,7 +169,12 @@ export function getPost(postId: string): ActionFuncAsync<Post> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPost(post: Post, files: any[] = []): ActionFuncAsync {
|
export type CreatePostReturnType = {
|
||||||
|
created?: boolean;
|
||||||
|
pending?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPost(post: Post, files: any[] = [], afterSubmit?: (response: any) => void): ActionFuncAsync<CreatePostReturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const currentUserId = state.entities.users.currentUserId;
|
const currentUserId = state.entities.users.currentUserId;
|
||||||
@ -178,7 +184,7 @@ export function createPost(post: Post, files: any[] = []): ActionFuncAsync {
|
|||||||
let actions: AnyAction[] = [];
|
let actions: AnyAction[] = [];
|
||||||
|
|
||||||
if (PostSelectors.isPostIdSending(state, pendingPostId)) {
|
if (PostSelectors.isPostIdSending(state, pendingPostId)) {
|
||||||
return {data: true};
|
return {data: {pending: pendingPostId}};
|
||||||
}
|
}
|
||||||
|
|
||||||
let newPost = {
|
let newPost = {
|
||||||
@ -266,6 +272,8 @@ export function createPost(post: Post, files: any[] = []): ActionFuncAsync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(batchActions(actions, 'BATCH_CREATE_POST'));
|
dispatch(batchActions(actions, 'BATCH_CREATE_POST'));
|
||||||
|
afterSubmit?.({created});
|
||||||
|
return {data: {created}};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const data = {
|
const data = {
|
||||||
...newPost,
|
...newPost,
|
||||||
@ -288,107 +296,11 @@ export function createPost(post: Post, files: any[] = []): ActionFuncAsync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(batchActions(actions, 'BATCH_CREATE_POST_FAILED'));
|
dispatch(batchActions(actions, 'BATCH_CREATE_POST_FAILED'));
|
||||||
|
return {error};
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
|
|
||||||
return {data: true};
|
return {data: {created: true}};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createPostImmediately(post: Post, files: any[] = []): ActionFuncAsync<Post> {
|
|
||||||
return async (dispatch, getState) => {
|
|
||||||
const state = getState();
|
|
||||||
const currentUserId = state.entities.users.currentUserId;
|
|
||||||
const timestamp = Date.now();
|
|
||||||
const pendingPostId = `${currentUserId}:${timestamp}`;
|
|
||||||
|
|
||||||
let newPost: Post = {
|
|
||||||
...post,
|
|
||||||
pending_post_id: pendingPostId,
|
|
||||||
create_at: timestamp,
|
|
||||||
update_at: timestamp,
|
|
||||||
reply_count: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (post.root_id) {
|
|
||||||
newPost.reply_count = PostSelectors.getPostRepliesCount(state, post.root_id) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.length) {
|
|
||||||
const fileIds = files.map((file) => file.id);
|
|
||||||
|
|
||||||
newPost = {
|
|
||||||
...newPost,
|
|
||||||
file_ids: fileIds,
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: FileTypes.RECEIVED_FILES_FOR_POST,
|
|
||||||
postId: pendingPostId,
|
|
||||||
data: files,
|
|
||||||
});
|
|
||||||
dispatch({
|
|
||||||
type: ChannelTypes.INCREMENT_FILE_COUNT,
|
|
||||||
amount: files.length,
|
|
||||||
id: newPost.channel_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const crtEnabled = isCollapsedThreadsEnabled(state);
|
|
||||||
dispatch(receivedNewPost({
|
|
||||||
...newPost,
|
|
||||||
id: pendingPostId,
|
|
||||||
}, crtEnabled));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const created = await Client4.createPost({...newPost, create_at: 0});
|
|
||||||
newPost.id = created.id;
|
|
||||||
newPost.reply_count = created.reply_count;
|
|
||||||
} catch (error) {
|
|
||||||
forceLogoutIfNecessary(error, dispatch, getState);
|
|
||||||
dispatch({type: PostTypes.CREATE_POST_FAILURE, data: newPost, error});
|
|
||||||
dispatch(removePost({
|
|
||||||
...newPost,
|
|
||||||
id: pendingPostId,
|
|
||||||
}));
|
|
||||||
dispatch(logError(error));
|
|
||||||
return {error};
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions: AnyAction[] = [
|
|
||||||
receivedPost(newPost, crtEnabled),
|
|
||||||
{
|
|
||||||
type: PostTypes.CREATE_POST_SUCCESS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ChannelTypes.INCREMENT_TOTAL_MSG_COUNT,
|
|
||||||
data: {
|
|
||||||
channelId: newPost.channel_id,
|
|
||||||
amount: 1,
|
|
||||||
amountRoot: newPost.root_id === '' ? 1 : 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,
|
|
||||||
data: {
|
|
||||||
channelId: newPost.channel_id,
|
|
||||||
amount: 1,
|
|
||||||
amountRoot: newPost.root_id === '' ? 1 : 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (files) {
|
|
||||||
actions.push({
|
|
||||||
type: FileTypes.RECEIVED_FILES_FOR_POST,
|
|
||||||
postId: newPost.id,
|
|
||||||
data: files,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(batchActions(actions));
|
|
||||||
|
|
||||||
return {data: newPost};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,7 +501,12 @@ export function unpinPost(postId: string): ActionFuncAsync {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addReaction(postId: string, emojiName: string): ActionFuncAsync {
|
export type SubmitReactionReturnType = {
|
||||||
|
reaction?: Reaction;
|
||||||
|
removedReaction?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addReaction(postId: string, emojiName: string): ActionFuncAsync<SubmitReactionReturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const currentUserId = getState().entities.users.currentUserId;
|
const currentUserId = getState().entities.users.currentUserId;
|
||||||
|
|
||||||
@ -607,11 +524,11 @@ export function addReaction(postId: string, emojiName: string): ActionFuncAsync
|
|||||||
data: reaction,
|
data: reaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {data: true};
|
return {data: {reaction}};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeReaction(postId: string, emojiName: string): ActionFuncAsync {
|
export function removeReaction(postId: string, emojiName: string): ActionFuncAsync<SubmitReactionReturnType, GlobalState> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const currentUserId = getState().entities.users.currentUserId;
|
const currentUserId = getState().entities.users.currentUserId;
|
||||||
|
|
||||||
@ -628,7 +545,7 @@ export function removeReaction(postId: string, emojiName: string): ActionFuncAsy
|
|||||||
data: {user_id: currentUserId, post_id: postId, emoji_name: emojiName},
|
data: {user_id: currentUserId, post_id: postId, emoji_name: emojiName},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {data: true};
|
return {data: {removedReaction: true}};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import {openModal} from 'actions/views/modals';
|
|||||||
import {closeRightHandSide, selectPostById} from 'actions/views/rhs';
|
import {closeRightHandSide, selectPostById} from 'actions/views/rhs';
|
||||||
import {getSelectedPostId, getIsRhsOpen} from 'selectors/rhs';
|
import {getSelectedPostId, getIsRhsOpen} from 'selectors/rhs';
|
||||||
|
|
||||||
|
import AdvancedTextEditor from 'components/advanced_text_editor/advanced_text_editor';
|
||||||
import ChannelInviteModal from 'components/channel_invite_modal';
|
import ChannelInviteModal from 'components/channel_invite_modal';
|
||||||
import ChannelMembersModal from 'components/channel_members_modal';
|
import ChannelMembersModal from 'components/channel_members_modal';
|
||||||
import {openPricingModal} from 'components/global_header/right_controls/plan_upgrade_button';
|
import {openPricingModal} from 'components/global_header/right_controls/plan_upgrade_button';
|
||||||
@ -26,7 +27,6 @@ import {formatText} from 'utils/text_formatting';
|
|||||||
import {useWebSocket, useWebSocketClient, WebSocketContext} from 'utils/use_websocket';
|
import {useWebSocket, useWebSocketClient, WebSocketContext} from 'utils/use_websocket';
|
||||||
import {imageURLForUser} from 'utils/utils';
|
import {imageURLForUser} from 'utils/utils';
|
||||||
|
|
||||||
import CreatePost from './exported_create_post';
|
|
||||||
import {openInteractiveDialog} from './interactive_dialog'; // This import has intentional side effects. Do not remove without research.
|
import {openInteractiveDialog} from './interactive_dialog'; // This import has intentional side effects. Do not remove without research.
|
||||||
import Textbox from './textbox';
|
import Textbox from './textbox';
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ window.Components = {
|
|||||||
BotBadge: BotTag,
|
BotBadge: BotTag,
|
||||||
StartTrialFormModal,
|
StartTrialFormModal,
|
||||||
ThreadViewer,
|
ThreadViewer,
|
||||||
CreatePost,
|
|
||||||
PostMessagePreview,
|
PostMessagePreview,
|
||||||
|
AdvancedTextEditor,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is a prototype of the Product API for use by internal plugins only while we transition to the proper architecture
|
// This is a prototype of the Product API for use by internal plugins only while we transition to the proper architecture
|
||||||
|
Loading…
Reference in New Issue
Block a user