mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-52902 : Pasting text or image in center message input box shifts input box up unexpectedly (#23502)
This commit is contained in:
parent
0a897220a7
commit
0e31ecbbe8
@ -311,9 +311,8 @@ describe('components/AdvancedCreateComment', () => {
|
|||||||
expect(wrapper.state().serverError.message).toBe(testError1);
|
expect(wrapper.state().serverError.message).toBe(testError1);
|
||||||
expect(wrapper.state().draft.uploadsInProgress).toEqual([2, 3]);
|
expect(wrapper.state().draft.uploadsInProgress).toEqual([2, 3]);
|
||||||
|
|
||||||
// clientId = -1
|
|
||||||
const testError2 = 'test error 2';
|
const testError2 = 'test error 2';
|
||||||
instance.handleUploadError(testError2, -1, null, props.rootId);
|
instance.handleUploadError(testError2, '', null, props.rootId);
|
||||||
|
|
||||||
// should not call onUpdateCommentDraft
|
// should not call onUpdateCommentDraft
|
||||||
expect(updateCommentDraftWithRootId.mock.calls.length).toBe(1);
|
expect(updateCommentDraftWithRootId.mock.calls.length).toBe(1);
|
||||||
|
@ -1116,8 +1116,8 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleUploadError = (err: string | ServerError | null, clientId: string | number = -1, _?: string, rootId = '') => {
|
handleUploadError = (uploadError: string | ServerError | null, clientId?: string, _?: string, rootId = '') => {
|
||||||
if (clientId !== -1) {
|
if (clientId) {
|
||||||
const draft = {...this.draftsForPost[rootId]!};
|
const draft = {...this.draftsForPost[rootId]!};
|
||||||
const uploadsInProgress = [...draft.uploadsInProgress];
|
const uploadsInProgress = [...draft.uploadsInProgress];
|
||||||
|
|
||||||
@ -1137,16 +1137,13 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let serverError = err;
|
if (typeof uploadError === 'string') {
|
||||||
if (typeof serverError === 'string') {
|
if (uploadError.length !== 0) {
|
||||||
serverError = new Error(serverError);
|
this.setState({serverError: new Error(uploadError)});
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({serverError}, () => {
|
|
||||||
if (serverError && this.props.scrollToBottom) {
|
|
||||||
this.props.scrollToBottom();
|
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
this.setState({serverError: uploadError});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removePreview = (id: string) => {
|
removePreview = (id: string) => {
|
||||||
|
@ -1038,34 +1038,32 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
|||||||
this.handleDraftChange(draft, true);
|
this.handleDraftChange(draft, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleUploadError = (err: string | ServerError, clientId?: string, channelId?: string) => {
|
handleUploadError = (uploadError: string | ServerError | null, clientId?: string, channelId?: string) => {
|
||||||
let serverError = err;
|
if (clientId && channelId) {
|
||||||
if (typeof serverError === 'string') {
|
const draft = {...this.draftsForChannel[channelId]!};
|
||||||
serverError = new Error(serverError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!channelId || !clientId) {
|
if (draft.uploadsInProgress) {
|
||||||
this.setState({serverError});
|
const index = draft.uploadsInProgress.indexOf(clientId);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const draft = {...this.draftsForChannel[channelId]!};
|
if (index !== -1) {
|
||||||
|
const uploadsInProgress = draft.uploadsInProgress.filter((item, itemIndex) => index !== itemIndex);
|
||||||
if (draft.uploadsInProgress) {
|
const modifiedDraft = {
|
||||||
const index = draft.uploadsInProgress.indexOf(clientId);
|
...draft,
|
||||||
|
uploadsInProgress,
|
||||||
if (index !== -1) {
|
};
|
||||||
const uploadsInProgress = draft.uploadsInProgress.filter((item, itemIndex) => index !== itemIndex);
|
this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, modifiedDraft, channelId);
|
||||||
const modifiedDraft = {
|
this.draftsForChannel[channelId] = modifiedDraft;
|
||||||
...draft,
|
}
|
||||||
uploadsInProgress,
|
|
||||||
};
|
|
||||||
this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, modifiedDraft, channelId);
|
|
||||||
this.draftsForChannel[channelId] = modifiedDraft;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({serverError});
|
if (typeof uploadError === 'string') {
|
||||||
|
if (uploadError.length !== 0) {
|
||||||
|
this.setState({serverError: new Error(uploadError)});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({serverError: uploadError});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removePreview = (id: string) => {
|
removePreview = (id: string) => {
|
||||||
|
@ -88,7 +88,7 @@ type Props = {
|
|||||||
hideEmojiPicker: () => void;
|
hideEmojiPicker: () => void;
|
||||||
toggleAdvanceTextEditor: () => void;
|
toggleAdvanceTextEditor: () => void;
|
||||||
handleUploadProgress: (filePreviewInfo: FilePreviewInfo) => void;
|
handleUploadProgress: (filePreviewInfo: FilePreviewInfo) => void;
|
||||||
handleUploadError: (err: string | ServerError, clientId?: string, channelId?: string) => void;
|
handleUploadError: (err: string | ServerError | null, clientId?: string, channelId?: string) => void;
|
||||||
handleFileUploadComplete: (fileInfos: FileInfo[], clientIds: string[], channelId: string, rootId?: string) => void;
|
handleFileUploadComplete: (fileInfos: FileInfo[], clientIds: string[], channelId: string, rootId?: string) => void;
|
||||||
handleUploadStart: (clientIds: string[], channelId: string) => void;
|
handleUploadStart: (clientIds: string[], channelId: string) => void;
|
||||||
handleFileUploadChange: () => void;
|
handleFileUploadChange: () => void;
|
||||||
@ -199,17 +199,6 @@ const AdvanceTextEditor = ({
|
|||||||
setKeepEditorInFocus(true);
|
setKeepEditorInFocus(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let serverErrorJsx = null;
|
|
||||||
if (serverError) {
|
|
||||||
serverErrorJsx = (
|
|
||||||
<MessageSubmitError
|
|
||||||
error={serverError}
|
|
||||||
submittedMessage={serverError.submittedMessage}
|
|
||||||
handleSubmit={handleSubmit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let attachmentPreview = null;
|
let attachmentPreview = null;
|
||||||
if (!readOnlyChannel && (draft.fileInfos.length > 0 || draft.uploadsInProgress.length > 0)) {
|
if (!readOnlyChannel && (draft.fileInfos.length > 0 || draft.uploadsInProgress.length > 0)) {
|
||||||
attachmentPreview = (
|
attachmentPreview = (
|
||||||
@ -551,12 +540,20 @@ const AdvanceTextEditor = ({
|
|||||||
<div
|
<div
|
||||||
id='postCreateFooter'
|
id='postCreateFooter'
|
||||||
role='form'
|
role='form'
|
||||||
className={classNames('AdvancedTextEditor__footer', {
|
className={classNames('AdvancedTextEditor__footer', {'AdvancedTextEditor__footer--has-error': postError || serverError})}
|
||||||
'AdvancedTextEditor__footer--has-error': postError || serverError,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{postError && <label className={classNames('post-error', {errorClass})}>{postError}</label>}
|
{postError && (
|
||||||
{serverErrorJsx}
|
<label className={classNames('post-error', {errorClass})}>
|
||||||
|
{postError}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
{serverError && (
|
||||||
|
<MessageSubmitError
|
||||||
|
error={serverError}
|
||||||
|
submittedMessage={serverError.submittedMessage}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<MsgTyping
|
<MsgTyping
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
postId={postId}
|
postId={postId}
|
||||||
|
@ -3,17 +3,15 @@
|
|||||||
|
|
||||||
import React, {MouseEvent, DragEvent, ChangeEvent} from 'react';
|
import React, {MouseEvent, DragEvent, ChangeEvent} from 'react';
|
||||||
|
|
||||||
|
import {FileInfo} from '@mattermost/types/files';
|
||||||
|
|
||||||
import {General} from 'mattermost-redux/constants';
|
import {General} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
import {clearFileInput} from 'utils/utils';
|
|
||||||
|
|
||||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
|
||||||
|
|
||||||
import FileUpload, {FileUpload as FileUploadClass} from 'components/file_upload/file_upload';
|
import FileUpload, {FileUpload as FileUploadClass} from 'components/file_upload/file_upload';
|
||||||
|
|
||||||
|
import {clearFileInput} from 'utils/utils';
|
||||||
import {FilesWillUploadHook} from 'types/store/plugins';
|
import {FilesWillUploadHook} from 'types/store/plugins';
|
||||||
|
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||||
import {FileInfo} from '@mattermost/types/files';
|
|
||||||
|
|
||||||
const generatedIdRegex = /[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/;
|
const generatedIdRegex = /[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/;
|
||||||
|
|
||||||
@ -266,7 +264,7 @@ describe('components/FileUpload', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledTimes(1);
|
expect(baseProps.onUploadError).toHaveBeenCalledTimes(1);
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledWith('');
|
expect(baseProps.onUploadError).toHaveBeenCalledWith(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should error max upload files', () => {
|
test('should error max upload files', () => {
|
||||||
@ -286,7 +284,7 @@ describe('components/FileUpload', () => {
|
|||||||
expect(baseProps.onUploadStart).toBeCalledWith([], props.channelId);
|
expect(baseProps.onUploadStart).toBeCalledWith([], props.channelId);
|
||||||
|
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledTimes(2);
|
expect(baseProps.onUploadError).toHaveBeenCalledTimes(2);
|
||||||
expect(baseProps.onUploadError.mock.calls[0][0]).toEqual('');
|
expect(baseProps.onUploadError.mock.calls[0][0]).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should error max upload files', () => {
|
test('should error max upload files', () => {
|
||||||
@ -306,7 +304,7 @@ describe('components/FileUpload', () => {
|
|||||||
expect(baseProps.onUploadStart).toBeCalledWith([], props.channelId);
|
expect(baseProps.onUploadStart).toBeCalledWith([], props.channelId);
|
||||||
|
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledTimes(2);
|
expect(baseProps.onUploadError).toHaveBeenCalledTimes(2);
|
||||||
expect(baseProps.onUploadError.mock.calls[0][0]).toEqual('');
|
expect(baseProps.onUploadError.mock.calls[0][0]).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should error max too large files', () => {
|
test('should error max too large files', () => {
|
||||||
@ -324,7 +322,7 @@ describe('components/FileUpload', () => {
|
|||||||
expect(baseProps.onUploadStart).toBeCalledWith([], baseProps.channelId);
|
expect(baseProps.onUploadStart).toBeCalledWith([], baseProps.channelId);
|
||||||
|
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledTimes(2);
|
expect(baseProps.onUploadError).toHaveBeenCalledTimes(2);
|
||||||
expect(baseProps.onUploadError.mock.calls[0][0]).toEqual('');
|
expect(baseProps.onUploadError.mock.calls[0][0]).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should functions when handleChange is called', () => {
|
test('should functions when handleChange is called', () => {
|
||||||
@ -358,7 +356,7 @@ describe('components/FileUpload', () => {
|
|||||||
instance.handleDrop(e);
|
instance.handleDrop(e);
|
||||||
|
|
||||||
expect(baseProps.onUploadError).toBeCalled();
|
expect(baseProps.onUploadError).toBeCalled();
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledWith('');
|
expect(baseProps.onUploadError).toHaveBeenCalledWith(null);
|
||||||
|
|
||||||
expect(instance.uploadFiles).toBeCalled();
|
expect(instance.uploadFiles).toBeCalled();
|
||||||
expect(instance.uploadFiles).toHaveBeenCalledWith(e.dataTransfer.files);
|
expect(instance.uploadFiles).toHaveBeenCalledWith(e.dataTransfer.files);
|
||||||
@ -386,7 +384,7 @@ describe('components/FileUpload', () => {
|
|||||||
expect(baseProps.onUploadStart).toHaveBeenCalledTimes(0);
|
expect(baseProps.onUploadStart).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledTimes(1);
|
expect(baseProps.onUploadError).toHaveBeenCalledTimes(1);
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledWith('');
|
expect(baseProps.onUploadError).toHaveBeenCalledWith(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('FilesWillUploadHook - should reject one file and allow one file', () => {
|
test('FilesWillUploadHook - should reject one file and allow one file', () => {
|
||||||
@ -409,6 +407,6 @@ describe('components/FileUpload', () => {
|
|||||||
expect(baseProps.onUploadStart).toHaveBeenCalledWith([expect.stringMatching(generatedIdRegex)], props.channelId);
|
expect(baseProps.onUploadStart).toHaveBeenCalledWith([expect.stringMatching(generatedIdRegex)], props.channelId);
|
||||||
|
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledTimes(1);
|
expect(baseProps.onUploadError).toHaveBeenCalledTimes(1);
|
||||||
expect(baseProps.onUploadError).toHaveBeenCalledWith('');
|
expect(baseProps.onUploadError).toHaveBeenCalledWith(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -120,7 +120,7 @@ export type Props = {
|
|||||||
/**
|
/**
|
||||||
* Function to be called when upload fails
|
* Function to be called when upload fails
|
||||||
*/
|
*/
|
||||||
onUploadError: (err: string | ServerError, clientId?: string, channelId?: string, currentRootId?: string) => void;
|
onUploadError: (err: string | ServerError | null, clientId?: string, channelId?: string, currentRootId?: string) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when file upload starts
|
* Function to be called when file upload starts
|
||||||
@ -222,13 +222,13 @@ export class FileUpload extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
pluginUploadFiles = (files: File[]) => {
|
pluginUploadFiles = (files: File[]) => {
|
||||||
// clear any existing errors
|
// clear any existing errors
|
||||||
this.props.onUploadError('');
|
this.props.onUploadError(null);
|
||||||
this.uploadFiles(files);
|
this.uploadFiles(files);
|
||||||
};
|
};
|
||||||
|
|
||||||
checkPluginHooksAndUploadFiles = (files: FileList | File[]) => {
|
checkPluginHooksAndUploadFiles = (files: FileList | File[]) => {
|
||||||
// clear any existing errors
|
// clear any existing errors
|
||||||
this.props.onUploadError('');
|
this.props.onUploadError(null);
|
||||||
|
|
||||||
let sortedFiles = Array.from(files).sort((a, b) => a.name.localeCompare(b.name, this.props.locale, {numeric: true}));
|
let sortedFiles = Array.from(files).sort((a, b) => a.name.localeCompare(b.name, this.props.locale, {numeric: true}));
|
||||||
|
|
||||||
@ -335,7 +335,7 @@ export class FileUpload extends PureComponent<Props, State> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onUploadError('');
|
this.props.onUploadError(null);
|
||||||
|
|
||||||
const items = e.dataTransfer.items || [];
|
const items = e.dataTransfer.items || [];
|
||||||
const droppedFiles = e.dataTransfer.files;
|
const droppedFiles = e.dataTransfer.files;
|
||||||
@ -460,7 +460,7 @@ export class FileUpload extends PureComponent<Props, State> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onUploadError('');
|
this.props.onUploadError(null);
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
for (let i = 0; i < e.clipboardData.items.length; i++) {
|
for (let i = 0; i < e.clipboardData.items.length; i++) {
|
||||||
|
@ -1,68 +1,56 @@
|
|||||||
// 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, {ReactFragment} from 'react';
|
import React, {MouseEventHandler} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import {ServerError} from '@mattermost/types/errors';
|
import {ServerError} from '@mattermost/types/errors';
|
||||||
|
|
||||||
import {isErrorInvalidSlashCommand} from 'utils/post_utils';
|
import {isErrorInvalidSlashCommand} from 'utils/post_utils';
|
||||||
|
|
||||||
interface MessageSubmitErrorProps {
|
interface Props {
|
||||||
error: ServerError;
|
error: ServerError;
|
||||||
handleSubmit: (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
|
handleSubmit: MouseEventHandler<HTMLAnchorElement>;
|
||||||
submittedMessage?: string;
|
submittedMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageSubmitError extends React.PureComponent<MessageSubmitErrorProps> {
|
function MessageSubmitError(props: Props) {
|
||||||
public renderSlashCommandError = (): string | ReactFragment => {
|
if (isErrorInvalidSlashCommand(props.error)) {
|
||||||
if (!this.props.submittedMessage) {
|
const slashCommand = props.submittedMessage?.split(' ')[0];
|
||||||
return this.props.error.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
const command = this.props.submittedMessage.split(' ')[0];
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<FormattedMessage
|
|
||||||
id='message_submit_error.invalidCommand'
|
|
||||||
defaultMessage="Command with a trigger of ''{command}'' not found. "
|
|
||||||
values={{
|
|
||||||
command,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
onClick={this.props.handleSubmit}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='message_submit_error.sendAsMessageLink'
|
|
||||||
defaultMessage='Click here to send as a message.'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element | null {
|
|
||||||
const error = this.props.error;
|
|
||||||
|
|
||||||
if (!error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let errorContent: string | ReactFragment = error.message;
|
|
||||||
if (isErrorInvalidSlashCommand(error)) {
|
|
||||||
errorContent = this.renderSlashCommandError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='has-error'>
|
<div className='has-error'>
|
||||||
<label className='control-label'>
|
<label className='control-label'>
|
||||||
{errorContent}
|
<FormattedMessage
|
||||||
|
id='message_submit_error.invalidCommand'
|
||||||
|
defaultMessage="Command with a trigger of ''{slashCommand}'' not found. "
|
||||||
|
values={{
|
||||||
|
slashCommand,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={props.handleSubmit}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='message_submit_error.sendAsMessageLink'
|
||||||
|
defaultMessage='Click here to send as a message.'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.error?.message?.trim()?.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='has-error'>
|
||||||
|
<label className='control-label'>{props.error.message.trim()}</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MessageSubmitError;
|
export default MessageSubmitError;
|
||||||
|
Loading…
Reference in New Issue
Block a user