From 81d41a1626ac4933e3bf6076e1896964c4b15f8d Mon Sep 17 00:00:00 2001 From: Vladimir Lebedev Date: Tue, 31 Mar 2020 16:58:11 +0300 Subject: [PATCH] =?UTF-8?q?MM-9915=20-=20Transition=20showPreview=20away?= =?UTF-8?q?=20from=20being=20mutable=20state=20on=20the=E2=80=A6=20(#4651)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MM-9915 - Transition showPreview away from being mutable state on the textbox component * fix variable naming * refactor: create textbox redux flow * fix errors after merge * fix bug on edit channel header page with updatePreview * fix bugs when textbox doesn't reset on channel change and on rhs open/close * move actions triggers to appropriate functions * fix current issues Co-authored-by: mattermod --- actions/views/textbox.js | 31 +++++++ components/create_comment/create_comment.jsx | 36 +++++--- .../create_comment/create_comment.test.jsx | 14 ++-- components/create_comment/index.js | 6 +- components/create_post/create_post.jsx | 34 +++++--- components/create_post/create_post.test.jsx | 6 +- components/create_post/index.js | 4 + .../edit_channel_header_modal.jsx | 22 +++-- .../edit_channel_header_modal.test.jsx | 2 + components/edit_channel_header_modal/index.js | 4 + .../edit_post_modal/edit_post_modal.jsx | 20 +++-- .../edit_post_modal/edit_post_modal.test.jsx | 31 +++++-- components/edit_post_modal/index.js | 4 + reducers/views/index.js | 2 + reducers/views/rhs.test.js | 1 - reducers/views/textbox.js | 49 +++++++++++ reducers/views/textbox.test.js | 84 +++++++++++++++++++ selectors/views/textbox.js | 18 ++++ utils/constants.jsx | 5 ++ 19 files changed, 316 insertions(+), 57 deletions(-) create mode 100644 actions/views/textbox.js create mode 100644 reducers/views/textbox.js create mode 100644 reducers/views/textbox.test.js create mode 100644 selectors/views/textbox.js diff --git a/actions/views/textbox.js b/actions/views/textbox.js new file mode 100644 index 000000000000..fc5bbcd6932d --- /dev/null +++ b/actions/views/textbox.js @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import {ActionTypes} from '../../utils/constants'; + +export function setShowPreviewOnCreateComment(showPreview) { + return { + type: ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_COMMENT, + showPreview, + }; +} + +export function setShowPreviewOnCreatePost(showPreview) { + return { + type: ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_POST, + showPreview, + }; +} + +export function setShowPreviewOnEditChannelHeaderModal(showPreview) { + return { + type: ActionTypes.SET_SHOW_PREVIEW_ON_EDIT_CHANNEL_HEADER_MODAL, + showPreview, + }; +} + +export function setShowPreviewOnEditPostModal(showPreview) { + return { + type: ActionTypes.SET_SHOW_PREVIEW_ON_EDIT_POST_MODAL, + showPreview, + }; +} diff --git a/components/create_comment/create_comment.jsx b/components/create_comment/create_comment.jsx index b83bf1847e1a..f3fd9cf9ebfe 100644 --- a/components/create_comment/create_comment.jsx +++ b/components/create_comment/create_comment.jsx @@ -200,6 +200,16 @@ class CreateComment extends React.PureComponent { * To determine if the current user can send special channel mentions */ useChannelMentions: PropTypes.bool.isRequired, + + /** + * Set show preview for textbox + */ + setShowPreview: PropTypes.func.isRequired, + + /** + * Should preview be showed + */ + shouldShowPreview: PropTypes.bool.isRequired, } static getDerivedStateFromProps(props, state) { @@ -230,13 +240,11 @@ class CreateComment extends React.PureComponent { showPostDeletedModal: false, showConfirmModal: false, showEmojiPicker: false, - showPreview: false, channelTimezoneCount: 0, uploadsProgressPercent: {}, renderScrollbar: false, suggestionListStyle: 'top', }; - this.lastBlurAt = 0; this.draftsForPost = {}; this.doInitialScrollToBottom = false; @@ -245,6 +253,8 @@ class CreateComment extends React.PureComponent { componentDidMount() { this.props.clearCommentDraftUploads(); this.props.onResetHistoryIndex(); + this.props.setShowPreview(false); + this.focusTextbox(); document.addEventListener('paste', this.pasteHandler); document.addEventListener('keydown', this.focusTextboxIfNecessary); @@ -274,7 +284,7 @@ class CreateComment extends React.PureComponent { } // Focus on textbox when returned from preview mode - if (prevState.showPreview && !this.state.showPreview) { + if (prevProps.shouldShowPreview && !this.props.shouldShowPreview) { this.focusTextbox(); } @@ -288,8 +298,8 @@ class CreateComment extends React.PureComponent { } } - updatePreview = (newState) => { - this.setState({showPreview: newState}); + setShowPreview = (newPreviewValue) => { + this.props.setShowPreview(newPreviewValue); } focusTextboxIfNecessary = (e) => { @@ -437,7 +447,7 @@ class CreateComment extends React.PureComponent { handleSubmit = async (e) => { e.preventDefault(); - this.updatePreview(false); + this.setShowPreview(false); const {channelMembersCount, enableConfirmNotificationsToChannel, useChannelMentions, isTimezoneEnabled} = this.props; const {draft} = this.state; @@ -556,7 +566,7 @@ class CreateComment extends React.PureComponent { this.handleSubmit(e); } - this.updatePreview(false); + this.setShowPreview(false); setTimeout(() => { this.focusTextbox(); }); @@ -620,7 +630,7 @@ class CreateComment extends React.PureComponent { Utils.isKeyPressed(e, Constants.KeyCodes.ENTER) && (e.ctrlKey || e.metaKey) ) { - this.updatePreview(false); + this.setShowPreview(false); this.commentMsgKeyPress(e); return; } @@ -933,7 +943,7 @@ class CreateComment extends React.PureComponent { } let fileUpload; - if (!readOnlyChannel && !this.state.showPreview) { + if (!readOnlyChannel && !this.props.shouldShowPreview) { fileUpload = ( diff --git a/components/create_comment/create_comment.test.jsx b/components/create_comment/create_comment.test.jsx index e7611690fc51..0ab0ea8aa039 100644 --- a/components/create_comment/create_comment.test.jsx +++ b/components/create_comment/create_comment.test.jsx @@ -38,6 +38,8 @@ describe('components/CreateComment', () => { onMoveHistoryIndexForward: jest.fn(), onEditLatestPost: jest.fn(), resetCreatePostRequest: jest.fn(), + setShowPreview: jest.fn(), + shouldShowPreview: false, readOnlyChannel: false, enableEmojiPicker: true, enableGifPicker: true, @@ -1149,18 +1151,16 @@ describe('components/CreateComment', () => { ); const instance = wrapper.instance(); instance.focusTextbox = jest.fn(); - - expect(wrapper.state('showPreview')).toBe(false); expect(instance.focusTextbox).not.toBeCalled(); - instance.updatePreview(true); + instance.setShowPreview(true); - expect(wrapper.state('showPreview')).toBe(true); + expect(baseProps.setShowPreview).toHaveBeenCalledWith(true); expect(instance.focusTextbox).not.toBeCalled(); - instance.updatePreview(false); - - expect(wrapper.state('showPreview')).toBe(false); + wrapper.setProps({shouldShowPreview: true}); + expect(instance.focusTextbox).not.toBeCalled(); + wrapper.setProps({shouldShowPreview: false}); expect(instance.focusTextbox).toBeCalled(); }); }); diff --git a/components/create_comment/index.js b/components/create_comment/index.js index 7d2b5333fb79..1f0203474fa5 100644 --- a/components/create_comment/index.js +++ b/components/create_comment/index.js @@ -27,6 +27,8 @@ import { } from 'actions/views/create_comment'; import {emitShortcutReactToLastPostFrom} from 'actions/post_actions'; import {getPostDraft, getIsRhsExpanded, getSelectedPostFocussedAt} from 'selectors/rhs'; +import {showPreviewOnCreateComment} from 'selectors/views/textbox'; +import {setShowPreviewOnCreateComment} from 'actions/views/textbox'; import CreateComment from './create_comment.jsx'; @@ -84,6 +86,7 @@ function makeMapStateToProps() { selectedPostFocussedAt: getSelectedPostFocussedAt(state), canPost, useChannelMentions, + shouldShowPreview: showPreviewOnCreateComment(state), }; }; } @@ -137,7 +140,8 @@ function makeMapDispatchToProps() { onEditLatestPost, resetCreatePostRequest, getChannelTimezones, - emitShortcutReactToLastPostFrom + emitShortcutReactToLastPostFrom, + setShowPreview: setShowPreviewOnCreateComment, }, dispatch); }; } diff --git a/components/create_post/create_post.jsx b/components/create_post/create_post.jsx index 7c598483c4fd..e89bcf983444 100644 --- a/components/create_post/create_post.jsx +++ b/components/create_post/create_post.jsx @@ -185,8 +185,19 @@ class CreatePost extends React.PureComponent { useChannelMentions: PropTypes.bool.isRequired, intl: intlShape.isRequired, + + /** + * Should preview be showed + */ + shouldShowPreview: PropTypes.bool.isRequired, + actions: PropTypes.shape({ + /** + * Set show preview for textbox + */ + setShowPreview: PropTypes.func.isRequired, + /** * func called after message submit. */ @@ -279,7 +290,6 @@ class CreatePost extends React.PureComponent { message: props.draft.message, submitting: false, serverError: null, - showPreview: false, }; } return updatedState; @@ -295,7 +305,6 @@ class CreatePost extends React.PureComponent { showEmojiPicker: false, showConfirmModal: false, channelTimezoneCount: 0, - showPreview: false, uploadsProgressPercent: {}, renderScrollbar: false, currentChannel: props.currentChannel, @@ -309,6 +318,7 @@ class CreatePost extends React.PureComponent { componentDidMount() { this.onOrientationChange(); + this.props.actions.setShowPreview(false); this.props.actions.clearDraftUploads(StoragePrefixes.DRAFT, (key, value) => { if (value) { return {...value, uploadsInProgress: []}; @@ -328,6 +338,10 @@ class CreatePost extends React.PureComponent { this.focusTextbox(); } + if (this.props.currentChannel.id !== prevProps.currentChannel.id) { + this.props.actions.setShowPreview(false); + } + // Focus on textbox when emoji picker is closed if (prevState.showEmojiPicker && !this.state.showEmojiPicker) { this.focusTextbox(); @@ -345,8 +359,8 @@ class CreatePost extends React.PureComponent { } } - updatePreview = (newState) => { - this.setState({showPreview: newState}); + setShowPreview = (newPreviewValue) => { + this.props.actions.setShowPreview(newPreviewValue); } setOrientationListeners = () => { @@ -701,7 +715,7 @@ class CreatePost extends React.PureComponent { this.handleSubmit(e); } - this.updatePreview(false); + this.setShowPreview(false); } this.emitTypingEvent(); @@ -1237,7 +1251,7 @@ class CreatePost extends React.PureComponent { } let fileUpload; - if (!readOnlyChannel && !this.state.showPreview) { + if (!readOnlyChannel && !this.props.shouldShowPreview) { fileUpload = ( diff --git a/components/create_post/create_post.test.jsx b/components/create_post/create_post.test.jsx index e30731098cb2..c5e54dece0ef 100644 --- a/components/create_post/create_post.test.jsx +++ b/components/create_post/create_post.test.jsx @@ -73,6 +73,7 @@ const actionsProp = { setDraft: jest.fn(), setEditingPost: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), executeCommand: async () => { return {data: true}; }, @@ -133,6 +134,7 @@ function createPost({ rhsExpanded={false} emojiMap={emojiMap} badConnection={false} + shouldShowPreview={false} isTimezoneEnabled={false} canPost={true} useChannelMentions={true} @@ -265,10 +267,6 @@ describe('components/create_post', () => { const wrapper = shallowWithIntl(createPost()); wrapper.instance().refs = {textbox: {getWrappedInstance: () => ({blur: jest.fn()})}}; - wrapper.setState({ - showPreview: false, - }); - const postTextbox = wrapper.find('#post_textbox'); postTextbox.simulate('KeyPress', {key: KeyCodes.ENTER[0], preventDefault: jest.fn(), persist: jest.fn()}); expect(GlobalActions.emitLocalUserTypingEvent).toHaveBeenCalledWith(currentChannelProp.id, ''); diff --git a/components/create_post/index.js b/components/create_post/index.js index e69fdd8f96b1..1b21605e21af 100644 --- a/components/create_post/index.js +++ b/components/create_post/index.js @@ -32,9 +32,11 @@ import {connectionErrorCount} from 'selectors/views/system'; import {addReaction, createPost, setEditingPost, emitShortcutReactToLastPostFrom} from 'actions/post_actions.jsx'; import {scrollPostListToBottom} from 'actions/views/channel'; import {selectPostFromRightHandSideSearchByPostId} from 'actions/views/rhs'; +import {setShowPreviewOnCreatePost} from 'actions/views/textbox'; import {executeCommand} from 'actions/command'; import {runMessageWillBePostedHooks, runSlashCommandWillBePostedHooks} from 'actions/hooks'; import {getPostDraft, getIsRhsExpanded} from 'selectors/rhs'; +import {showPreviewOnCreatePost} from 'selectors/views/textbox'; import {getCurrentLocale} from 'selectors/i18n'; import {getEmojiMap, getShortcutReactToLastPostEmittedFrom} from 'selectors/emojis'; import {setGlobalItem, actionOnGlobalItemsWithPrefix} from 'actions/storage'; @@ -109,6 +111,7 @@ function makeMapStateToProps() { shortcutReactToLastPostEmittedFrom, canPost, useChannelMentions, + shouldShowPreview: showPreviewOnCreatePost(state), }; }; } @@ -139,6 +142,7 @@ function mapDispatchToProps(dispatch) { runMessageWillBePostedHooks, runSlashCommandWillBePostedHooks, scrollPostListToBottom, + setShowPreview: setShowPreviewOnCreatePost, }, dispatch), }; } diff --git a/components/edit_channel_header_modal/edit_channel_header_modal.jsx b/components/edit_channel_header_modal/edit_channel_header_modal.jsx index cd8c526b7b3d..5121b25e434b 100644 --- a/components/edit_channel_header_modal/edit_channel_header_modal.jsx +++ b/components/edit_channel_header_modal/edit_channel_header_modal.jsx @@ -46,6 +46,11 @@ class EditChannelHeaderModal extends React.PureComponent { */ ctrlSend: PropTypes.bool.isRequired, + /* + * Should preview be showed + */ + shouldShowPreview: PropTypes.bool.isRequired, + /* * Collection of redux actions */ @@ -57,6 +62,12 @@ class EditChannelHeaderModal extends React.PureComponent { * patch channel redux-action */ patchChannel: PropTypes.func.isRequired, + + /** + * Set show preview for textbox + */ + setShowPreview: PropTypes.func.isRequired, + }).isRequired, } @@ -64,7 +75,6 @@ class EditChannelHeaderModal extends React.PureComponent { super(props); this.state = { - preview: false, header: props.channel.header, saving: false, }; @@ -76,8 +86,8 @@ class EditChannelHeaderModal extends React.PureComponent { } } - updatePreview = (newState) => { - this.setState({preview: newState}); + setShowPreview = (newState) => { + this.props.actions.setShowPreview(newState); } handleChange = (e) => { @@ -228,15 +238,15 @@ class EditChannelHeaderModal extends React.PureComponent { id='edit_textbox' ref='editChannelHeaderTextbox' characterLimit={1024} - preview={this.state.preview} + preview={this.props.shouldShowPreview} />
diff --git a/components/edit_channel_header_modal/edit_channel_header_modal.test.jsx b/components/edit_channel_header_modal/edit_channel_header_modal.test.jsx index bd49a249b081..b613230962ae 100644 --- a/components/edit_channel_header_modal/edit_channel_header_modal.test.jsx +++ b/components/edit_channel_header_modal/edit_channel_header_modal.test.jsx @@ -32,8 +32,10 @@ describe('components/EditChannelHeaderModal', () => { channel, ctrlSend: false, show: false, + shouldShowPreview: false, actions: { closeModal: jest.fn(), + setShowPreview: jest.fn(), patchChannel: jest.fn().mockResolvedValueOnce({error: serverError}).mockResolvedValue({}), }, }; diff --git a/components/edit_channel_header_modal/index.js b/components/edit_channel_header_modal/index.js index 54032e9b1740..df2b0173ca85 100644 --- a/components/edit_channel_header_modal/index.js +++ b/components/edit_channel_header_modal/index.js @@ -8,6 +8,8 @@ import {patchChannel} from 'mattermost-redux/actions/channels'; import {Preferences} from 'mattermost-redux/constants'; import {closeModal} from 'actions/views/modals'; +import {setShowPreviewOnEditChannelHeaderModal} from 'actions/views/textbox'; +import {showPreviewOnEditChannelHeaderModal} from 'selectors/views/textbox'; import {isModalOpen} from '../../selectors/views/modals'; import {ModalIdentifiers} from '../../utils/constants'; @@ -16,6 +18,7 @@ import EditChannelHeaderModal from './edit_channel_header_modal.jsx'; function mapStateToProps(state) { return { + shouldShowPreview: showPreviewOnEditChannelHeaderModal(state), show: isModalOpen(state, ModalIdentifiers.EDIT_CHANNEL_HEADER), ctrlSend: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'), }; @@ -26,6 +29,7 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ closeModal, patchChannel, + setShowPreview: setShowPreviewOnEditChannelHeaderModal, }, dispatch), }; } diff --git a/components/edit_post_modal/edit_post_modal.jsx b/components/edit_post_modal/edit_post_modal.jsx index 9b60b130e367..3b7dd6e97285 100644 --- a/components/edit_post_modal/edit_post_modal.jsx +++ b/components/edit_post_modal/edit_post_modal.jsx @@ -29,6 +29,7 @@ class EditPostModal extends React.PureComponent { config: PropTypes.object.isRequired, intl: intlShape.isRequired, maxPostSize: PropTypes.number.isRequired, + shouldShowPreview: PropTypes.bool.isRequired, editingPost: PropTypes.shape({ post: PropTypes.object, postId: PropTypes.string, @@ -44,6 +45,7 @@ class EditPostModal extends React.PureComponent { editPost: PropTypes.func.isRequired, hideEditPostModal: PropTypes.func.isRequired, openModal: PropTypes.func.isRequired, + setShowPreview: PropTypes.func.isRequired, }).isRequired, } @@ -51,7 +53,6 @@ class EditPostModal extends React.PureComponent { super(props); this.state = { - preview: false, editText: '', caretPosition: ''.length, postError: '', @@ -72,8 +73,8 @@ class EditPostModal extends React.PureComponent { return null; } - updatePreview = (newState) => { - this.setState({preview: newState}); + setShowPreview = (newPreviewValue) => { + this.props.actions.setShowPreview(newPreviewValue); } getContainer = () => { @@ -267,7 +268,7 @@ class EditPostModal extends React.PureComponent { } handleExit = () => { - this.setState({preview: false}); + this.props.actions.setShowPreview(false); } handleExited = () => { @@ -282,7 +283,8 @@ class EditPostModal extends React.PureComponent { } this.refocusId = null; - this.setState({editText: '', postError: '', errorClass: null, preview: false, showEmojiPicker: false, prevShowState: false}); + this.setState({editText: '', postError: '', errorClass: null, showEmojiPicker: false, prevShowState: false}); + this.props.actions.setShowPreview(false); } setEditboxRef = (ref) => { @@ -320,7 +322,7 @@ class EditPostModal extends React.PureComponent { let emojiPicker = null; const emojiButtonAriaLabel = formatMessage({id: 'emoji_picker.emojiPicker', defaultMessage: 'Emoji Picker'}).toLowerCase(); - if (this.props.config.EnableEmojiPicker === 'true' && !this.state.preview) { + if (this.props.config.EnableEmojiPicker === 'true' && !this.props.shouldShowPreview) { emojiPicker = (
{emojiPicker} @@ -412,9 +414,9 @@ class EditPostModal extends React.PureComponent {
diff --git a/components/edit_post_modal/edit_post_modal.test.jsx b/components/edit_post_modal/edit_post_modal.test.jsx index 9d76d1304bdb..cf0ccf9a3180 100644 --- a/components/edit_post_modal/edit_post_modal.test.jsx +++ b/components/edit_post_modal/edit_post_modal.test.jsx @@ -47,10 +47,12 @@ function createEditPost({canEditPost, canDeletePost, ctrlSend, config, license, addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; return ( { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; const editingPost = { postId: '123', @@ -114,6 +117,7 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; const wrapper = shallowWithIntl(createEditPost({actions})); @@ -226,13 +230,21 @@ describe('components/EditPostModal', () => { }); it('should hide the preview when exiting', () => { - const wrapper = shallowWithIntl(createEditPost()); + const actions = { + editPost: jest.fn(), + addMessageIntoHistory: jest.fn(), + hideEditPostModal: jest.fn(), + openModal: jest.fn(), + setShowPreview: jest.fn(), + }; + + const wrapper = shallowWithIntl(createEditPost({actions})); const instance = wrapper.instance(); - instance.updatePreview(true); - expect(wrapper.state().preview).toBe(true); + instance.setShowPreview(true); + expect(actions.setShowPreview).toHaveBeenCalledWith(true); instance.handleExit(); - expect(wrapper.state().preview).toBe(false); + expect(actions.setShowPreview).toHaveBeenCalledWith(false); }); it('should close without saving when post text is not changed', () => { @@ -241,6 +253,7 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; const wrapper = shallowWithIntl(createEditPost({actions})); const instance = wrapper.instance(); @@ -260,6 +273,7 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; var wrapper = shallowWithIntl(createEditPost({actions})); var instance = wrapper.instance(); @@ -309,6 +323,7 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; global.scrollTo = jest.fn(); const wrapper = shallowWithIntl(createEditPost({actions})); @@ -336,15 +351,16 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; const editingPost = {show: false}; const wrapper = shallowWithIntl(createEditPost({actions, editingPost})); const instance = wrapper.instance(); - wrapper.setState({editText: 'test', postError: 'test', errorClass: 'test', preview: true, showEmojiPicker: true}); + wrapper.setState({editText: 'test', postError: 'test', errorClass: 'test', showEmojiPicker: true}); instance.handleExited(); - expect(wrapper.state()).toEqual({editText: '', caretPosition: 0, postError: '', errorClass: null, preview: false, showEmojiPicker: false, prevShowState: false}); + expect(wrapper.state()).toEqual({editText: '', caretPosition: 0, postError: '', errorClass: null, showEmojiPicker: false, prevShowState: false}); }); it('should focus element on exit based on refocusId', () => { @@ -353,6 +369,7 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; const wrapper = shallowWithIntl(createEditPost({actions})); const instance = wrapper.instance(); @@ -464,6 +481,7 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; const wrapper = shallowWithIntl(createEditPost({actions, canEditPost: false})); wrapper.setState({editText: 'new message'}); @@ -489,6 +507,7 @@ describe('components/EditPostModal', () => { addMessageIntoHistory: jest.fn(), hideEditPostModal: jest.fn(), openModal: jest.fn(), + setShowPreview: jest.fn(), }; const wrapper = shallowWithIntl(createEditPost({actions, canDeletePost: false})); wrapper.setState({editText: ''}); diff --git a/components/edit_post_modal/index.js b/components/edit_post_modal/index.js index 06457fe4a368..320328ab9c10 100644 --- a/components/edit_post_modal/index.js +++ b/components/edit_post_modal/index.js @@ -13,6 +13,8 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {getBool} from 'mattermost-redux/selectors/entities/preferences'; import {openModal} from 'actions/views/modals'; +import {setShowPreviewOnEditPostModal} from 'actions/views/textbox'; +import {showPreviewOnEditPostModal} from 'selectors/views/textbox'; import {hideEditPostModal} from 'actions/post_actions'; import {editPost} from 'actions/views/posts'; import {getEditingPost} from 'selectors/posts'; @@ -41,6 +43,7 @@ function mapStateToProps(state) { ctrlSend: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'), config, editingPost, + shouldShowPreview: showPreviewOnEditPostModal(state), maxPostSize: parseInt(config.MaxPostSize, 10) || Constants.DEFAULT_CHARACTER_LIMIT, }; } @@ -52,6 +55,7 @@ function mapDispatchToProps(dispatch) { editPost, hideEditPostModal, openModal, + setShowPreview: setShowPreviewOnEditPostModal, }, dispatch), }; } diff --git a/reducers/views/index.js b/reducers/views/index.js index b2dd53e28c3d..20cae7275d12 100644 --- a/reducers/views/index.js +++ b/reducers/views/index.js @@ -19,6 +19,7 @@ import channelSelectorModal from './channel_selector_modal'; import settings from './settings'; import marketplace from './marketplace'; import channelSidebar from './channel_sidebar'; +import textbox from './textbox'; export default combineReducers({ admin, @@ -36,5 +37,6 @@ export default combineReducers({ channelSelectorModal, settings, marketplace, + textbox, channelSidebar, }); diff --git a/reducers/views/rhs.test.js b/reducers/views/rhs.test.js index 4c9b0fab13fd..09247768b938 100644 --- a/reducers/views/rhs.test.js +++ b/reducers/views/rhs.test.js @@ -22,7 +22,6 @@ describe('Reducers.RHS', () => { isMenuOpen: false, isSidebarOpen: false, isSidebarExpanded: false, - }; test('Initial state', () => { diff --git a/reducers/views/textbox.js b/reducers/views/textbox.js new file mode 100644 index 000000000000..45baf9314860 --- /dev/null +++ b/reducers/views/textbox.js @@ -0,0 +1,49 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {combineReducers} from 'redux'; + +import {ActionTypes} from 'utils/constants'; + +function shouldShowPreviewOnCreateComment(state = false, action) { + switch (action.type) { + case ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_COMMENT: + return action.showPreview; + default: + return state; + } +} + +function shouldShowPreviewOnCreatePost(state = false, action) { + switch (action.type) { + case ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_POST: + return action.showPreview; + default: + return state; + } +} + +function shouldShowPreviewOnEditChannelHeaderModal(state = false, action) { + switch (action.type) { + case ActionTypes.SET_SHOW_PREVIEW_ON_EDIT_CHANNEL_HEADER_MODAL: + return action.showPreview; + default: + return state; + } +} + +function shouldShowPreviewOnEditPostModal(state = false, action) { + switch (action.type) { + case ActionTypes.SET_SHOW_PREVIEW_ON_EDIT_POST_MODAL: + return action.showPreview; + default: + return state; + } +} + +export default combineReducers({ + shouldShowPreviewOnCreateComment, + shouldShowPreviewOnCreatePost, + shouldShowPreviewOnEditChannelHeaderModal, + shouldShowPreviewOnEditPostModal, +}); diff --git a/reducers/views/textbox.test.js b/reducers/views/textbox.test.js new file mode 100644 index 000000000000..db474ecb7a2d --- /dev/null +++ b/reducers/views/textbox.test.js @@ -0,0 +1,84 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import textboxReducer from 'reducers/views/textbox'; + +import {ActionTypes} from '../../utils/constants'; + +describe('Reducers.RHS', () => { + const initialState = { + shouldShowPreviewOnCreateComment: false, + shouldShowPreviewOnCreatePost: false, + shouldShowPreviewOnEditChannelHeaderModal: false, + shouldShowPreviewOnEditPostModal: false, + }; + + test('Initial state', () => { + const nextState = textboxReducer( + {}, + {} + ); + + expect(nextState).toEqual(initialState); + }); + + test('update show preview value on create comment', () => { + const nextState = textboxReducer( + {}, + { + type: ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_COMMENT, + showPreview: true, + } + ); + + expect(nextState).toEqual({ + ...initialState, + shouldShowPreviewOnCreateComment: true, + }); + }); + + test('update show preview value on create post', () => { + const nextState = textboxReducer( + {}, + { + type: ActionTypes.SET_SHOW_PREVIEW_ON_CREATE_POST, + showPreview: true, + } + ); + + expect(nextState).toEqual({ + ...initialState, + shouldShowPreviewOnCreatePost: true, + }); + }); + + test('update show preview value on edit channel header modal', () => { + const nextState = textboxReducer( + {}, + { + type: ActionTypes.SET_SHOW_PREVIEW_ON_EDIT_CHANNEL_HEADER_MODAL, + showPreview: true, + } + ); + + expect(nextState).toEqual({ + ...initialState, + shouldShowPreviewOnEditChannelHeaderModal: true, + }); + }); + + test('update show preview value on edit post modal', () => { + const nextState = textboxReducer( + {}, + { + type: ActionTypes.SET_SHOW_PREVIEW_ON_EDIT_POST_MODAL, + showPreview: true, + } + ); + + expect(nextState).toEqual({ + ...initialState, + shouldShowPreviewOnEditPostModal: true, + }); + }); +}); diff --git a/selectors/views/textbox.js b/selectors/views/textbox.js new file mode 100644 index 000000000000..220e13ba604d --- /dev/null +++ b/selectors/views/textbox.js @@ -0,0 +1,18 @@ + +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +export function showPreviewOnCreateComment(state) { + return state.views.textbox.shouldShowPreviewOnCreateComment; +} + +export function showPreviewOnCreatePost(state) { + return state.views.textbox.shouldShowPreviewOnCreatePost; +} + +export function showPreviewOnEditChannelHeaderModal(state) { + return state.views.textbox.shouldShowPreviewOnEditChannelHeaderModal; +} + +export function showPreviewOnEditPostModal(state) { + return state.views.textbox.shouldShowPreviewOnEditPostModal; +} diff --git a/utils/constants.jsx b/utils/constants.jsx index 27b4595cc3ff..2691fe7ad576 100644 --- a/utils/constants.jsx +++ b/utils/constants.jsx @@ -163,6 +163,11 @@ export const ActionTypes = keyMirror({ OPEN_LHS: null, CLOSE_LHS: null, + SET_SHOW_PREVIEW_ON_CREATE_COMMENT: null, + SET_SHOW_PREVIEW_ON_CREATE_POST: null, + SET_SHOW_PREVIEW_ON_EDIT_CHANNEL_HEADER_MODAL: null, + SET_SHOW_PREVIEW_ON_EDIT_POST_MODAL: null, + TOGGLE_RHS_MENU: null, OPEN_RHS_MENU: null, CLOSE_RHS_MENU: null,