From 9ff9b872bab3c6d6ffb9d3cba475b7ac4c1b4d99 Mon Sep 17 00:00:00 2001 From: Chris Duarte Date: Thu, 26 Oct 2017 07:48:52 -0700 Subject: [PATCH] Config to enable when clicking on username to fill input with @mention (#966) * Config to enable when clicking on username to fill input with @mention * Rename to experimental and move tenary from componet to variable * Use getCurrentChannelId in insertToPostDraft action * Change to work with rebase * Refactor thread reducer and blacklist currentThreadId * Review feedback * Review feedback 2 --- app/actions/views/channel.js | 56 +++- app/actions/views/thread.js | 8 + app/components/post/index.js | 5 +- app/components/post/post.js | 14 +- app/components/post_header/index.js | 5 +- app/components/post_header/post_header.js | 14 +- app/components/post_textbox/index.js | 8 +- app/components/post_textbox/post_textbox.js | 24 +- app/constants/view.js | 3 + app/reducers/views/channel.js | 289 +++++++++++-------- app/reducers/views/thread.js | 305 ++++++++++++-------- app/screens/code/index.js | 2 +- assets/base/config.json | 1 + 13 files changed, 464 insertions(+), 270 deletions(-) diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 76938ec16..e1dbd8e2f 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -18,6 +18,7 @@ import {savePreferences} from 'mattermost-redux/actions/preferences'; import {getTeamMembersByIds} from 'mattermost-redux/actions/teams'; import {getProfilesInChannel} from 'mattermost-redux/actions/users'; import {General, Preferences} from 'mattermost-redux/constants'; +import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; import { getChannelByName, getDirectChannelName, @@ -273,16 +274,67 @@ export function handleSelectChannel(channelId) { }; } -export function handlePostDraftChanged(channelId, postDraft) { +export function handlePostDraftChanged(channelId, draft) { return async (dispatch, getState) => { dispatch({ type: ViewTypes.POST_DRAFT_CHANGED, channelId, - postDraft + draft }, getState); }; } +export function handlePostDraftSelectionChanged(channelId, cursorPosition) { + return { + type: ViewTypes.POST_DRAFT_SELECTION_CHANGED, + channelId, + cursorPosition + }; +} + +export function insertToDraft(value) { + return (dispatch, getState) => { + const state = getState(); + const channelId = getCurrentChannelId(state); + const threadId = state.entities.posts.selectedPostId; + + let draft; + let cursorPosition; + let action; + if (state.views.thread.drafts[threadId]) { + const threadDraft = state.views.thread.drafts[threadId]; + draft = threadDraft.draft; + cursorPosition = threadDraft.cursorPosition; + action = { + type: ViewTypes.COMMENT_DRAFT_CHANGED, + rootId: threadId + }; + } else if (state.views.channel.drafts[channelId]) { + const channelDraft = state.views.channel.drafts[channelId]; + draft = channelDraft.draft; + cursorPosition = channelDraft.cursorPosition; + action = { + type: ViewTypes.POST_DRAFT_CHANGED, + channelId + }; + } + + let nextDraft = `${value}`; + if (cursorPosition > 0) { + const beginning = draft.slice(0, cursorPosition); + const end = draft.slice(cursorPosition); + nextDraft = `${beginning}${value}${end}`; + } + + if (action && nextDraft !== draft) { + dispatch({ + ...action, + draft: nextDraft + }); + } + }; +} + export function toggleDMChannel(otherUserId, visible) { return async (dispatch, getState) => { const state = getState(); diff --git a/app/actions/views/thread.js b/app/actions/views/thread.js index 49abb0c2a..b2e33896f 100644 --- a/app/actions/views/thread.js +++ b/app/actions/views/thread.js @@ -12,3 +12,11 @@ export function handleCommentDraftChanged(rootId, draft) { }, getState); }; } + +export function handleCommentDraftSelectionChanged(rootId, cursorPosition) { + return { + type: ViewTypes.COMMENT_DRAFT_SELECTION_CHANGED, + rootId, + cursorPosition + }; +} diff --git a/app/components/post/index.js b/app/components/post/index.js index a9f60fc2e..cab594c04 100644 --- a/app/components/post/index.js +++ b/app/components/post/index.js @@ -8,6 +8,7 @@ import {addReaction, createPost, deletePost, removePost} from 'mattermost-redux/ import {getPost} from 'mattermost-redux/selectors/entities/posts'; import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users'; +import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import Post from './post'; @@ -62,7 +63,9 @@ function mapDispatchToProps(dispatch) { addReaction, createPost, deletePost, - removePost + removePost, + setPostTooltipVisible, + insertToDraft }, dispatch) }; } diff --git a/app/components/post/post.js b/app/components/post/post.js index 0fb39cdd0..f18082daf 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -10,6 +10,7 @@ import { } from 'react-native'; import {injectIntl, intlShape} from 'react-intl'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; +import {isToolTipShowing} from 'react-native-tooltip'; import PostBody from 'app/components/post_body'; import PostHeader from 'app/components/post_header'; @@ -25,7 +26,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {canDeletePost, canEditPost, isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils'; import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; -import {isToolTipShowing} from 'react-native-tooltip'; +import Config from 'assets/config'; class Post extends PureComponent { static propTypes = { @@ -33,6 +34,7 @@ class Post extends PureComponent { addReaction: PropTypes.func.isRequired, createPost: PropTypes.func.isRequired, deletePost: PropTypes.func.isRequired, + insertToDraft: PropTypes.func.isRequired, removePost: PropTypes.func.isRequired }).isRequired, config: PropTypes.object.isRequired, @@ -117,6 +119,12 @@ class Post extends PureComponent { }); }; + autofillUserMention = (username) => { + // create a general action that checks for currentThreadId in the state and decides + // whether to insert to root or thread + this.props.actions.insertToDraft(`@${username} `); + } + handleEditDisable = () => { this.setState({canEdit: false}); }; @@ -330,6 +338,8 @@ class Post extends PureComponent { const selected = this.state && this.state.selected ? style.selected : null; const highlighted = highlight ? style.highlight : null; + const onUsernamePress = Config.ExperimentalUsernamePressIsMention ? this.autofillUserMention : this.viewUserProfile; + return ( @@ -349,7 +359,7 @@ class Post extends PureComponent { shouldRenderReplyButton={shouldRenderReplyButton} showFullDate={showFullDate} onPress={this.handleReply} - onViewUserProfile={this.viewUserProfile} + onUsernamePress={onUsernamePress} renderReplies={renderReplies} theme={theme} /> diff --git a/app/components/post_header/index.js b/app/components/post_header/index.js index 49660f117..3e84f2976 100644 --- a/app/components/post_header/index.js +++ b/app/components/post_header/index.js @@ -18,7 +18,7 @@ function makeMapStateToProps() { const {config} = state.entities.general; const post = getPost(state, ownProps.postId); const commentedOnUser = getUser(state, ownProps.commentedOnUserId); - const user = getUser(state, post.user_id); + const user = getUser(state, post.user_id) || {}; const teammateNameDisplay = getTeammateNameDisplaySetting(state); const militaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time'); @@ -34,7 +34,8 @@ function makeMapStateToProps() { isPendingOrFailedPost: isPostPendingOrFailed(post), isSystemMessage: isSystemMessage(post), overrideUsername: post.props && post.props.override_username, - theme: getTheme(state) + theme: getTheme(state), + username: user.username }; }; } diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js index fa0d089d8..63bef90c9 100644 --- a/app/components/post_header/post_header.js +++ b/app/components/post_header/post_header.js @@ -31,26 +31,30 @@ export default class PostHeader extends PureComponent { isSystemMessage: PropTypes.bool, militaryTime: PropTypes.bool, onPress: PropTypes.func, - onViewUserProfile: PropTypes.func, + onUsernamePress: PropTypes.func, overrideUsername: PropTypes.string, renderReplies: PropTypes.bool, shouldRenderReplyButton: PropTypes.bool, showFullDate: PropTypes.bool, - theme: PropTypes.object.isRequired + theme: PropTypes.object.isRequired, + username: PropTypes.string.isRequired }; static defaultProps = { commentCount: 0, onPress: emptyFunction, - onViewUserProfile: emptyFunction + onUsernamePress: emptyFunction }; + handleUsernamePress = () => { + this.props.onUsernamePress(this.props.username); + } + getDisplayName = (style) => { const { enablePostUsernameOverride, fromWebHook, isSystemMessage, - onViewUserProfile, overrideUsername } = this.props; @@ -80,7 +84,7 @@ export default class PostHeader extends PureComponent { ); } else if (this.props.displayName) { return ( - + {this.props.displayName} diff --git a/app/components/post_textbox/index.js b/app/components/post_textbox/index.js index 9bff4986e..4e4c99555 100644 --- a/app/components/post_textbox/index.js +++ b/app/components/post_textbox/index.js @@ -11,9 +11,9 @@ import {canUploadFilesOnMobile} from 'mattermost-redux/selectors/entities/genera import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {addReactionToLatestPost} from 'app/actions/views/emoji'; -import {handlePostDraftChanged} from 'app/actions/views/channel'; +import {handlePostDraftChanged, handlePostDraftSelectionChanged} from 'app/actions/views/channel'; import {handleClearFiles, handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload'; -import {handleCommentDraftChanged} from 'app/actions/views/thread'; +import {handleCommentDraftChanged, handleCommentDraftSelectionChanged} from 'app/actions/views/thread'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views'; @@ -44,7 +44,9 @@ function mapDispatchToProps(dispatch) { handlePostDraftChanged, handleRemoveLastFile, handleUploadFiles, - userTyping + userTyping, + handlePostDraftSelectionChanged, + handleCommentDraftSelectionChanged }, dispatch) }; } diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js index c845c08c9..857527700 100644 --- a/app/components/post_textbox/post_textbox.js +++ b/app/components/post_textbox/post_textbox.js @@ -39,7 +39,9 @@ class PostTextbox extends PureComponent { handleClearFiles: PropTypes.func.isRequired, handleRemoveLastFile: PropTypes.func.isRequired, handleUploadFiles: PropTypes.func.isRequired, - userTyping: PropTypes.func.isRequired + userTyping: PropTypes.func.isRequired, + handlePostDraftSelectionChanged: PropTypes.func.isRequired, + handleCommentDraftSelectionChanged: PropTypes.func.isRequired }).isRequired, canUploadFiles: PropTypes.bool.isRequired, channelId: PropTypes.string.isRequired, @@ -56,7 +58,6 @@ class PostTextbox extends PureComponent { static defaultProps = { files: [], - onSelectionChange: () => true, rootId: '', value: '' }; @@ -239,12 +240,6 @@ class PostTextbox extends PureComponent { actions.userTyping(channelId, rootId); }; - handleSelectionChange = (event) => { - if (this.autocomplete) { - this.autocomplete.handleSelectionChange(event); - } - }; - handleContentSizeChange = (event) => { let contentHeight = event.nativeEvent.layout.height; if (contentHeight < INITIAL_HEIGHT) { @@ -319,6 +314,17 @@ class PostTextbox extends PureComponent { return null; } + handlePostDraftSelectionChanged = (event) => { + const cursorPosition = event.nativeEvent.selection.end; + if (this.props.rootId) { + this.props.actions.handleCommentDraftSelectionChanged(this.props.rootId, cursorPosition); + } else { + this.props.actions.handlePostDraftSelectionChanged(this.props.channelId, cursorPosition); + } + + this.autocomplete.handleSelectionChange(event); + } + render() { const { canUploadFiles, @@ -382,7 +388,7 @@ class PostTextbox extends PureComponent { ref='input' value={textValue} onChangeText={this.handleTextChange} - onSelectionChange={this.handleSelectionChange} + onSelectionChange={this.handlePostDraftSelectionChanged} placeholder={intl.formatMessage(placeholder)} placeholderTextColor={changeOpacity('#000', 0.5)} multiline={true} diff --git a/app/constants/view.js b/app/constants/view.js index c07add7ea..24f3b4f99 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -19,6 +19,9 @@ const ViewTypes = keyMirror({ COMMENT_DRAFT_CHANGED: null, SEARCH_DRAFT_CHANGED: null, + POST_DRAFT_SELECTION_CHANGED: null, + COMMENT_DRAFT_SELECTION_CHANGED: null, + NOTIFICATION_IN_APP: null, NOTIFICATION_TAPPED: null, diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js index 46ddbdf71..870624819 100644 --- a/app/reducers/views/channel.js +++ b/app/reducers/views/channel.js @@ -18,156 +18,199 @@ function displayName(state = '', action) { } } -function drafts(state = {}, action) { - switch (action.type) { - case ViewTypes.POST_DRAFT_CHANGED: { - return { +function handlePostDraftChanged(state, action) { + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {draft: action.draft}) + }; +} + +function handlePostDraftSelectionChanged(state, action) { + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], { + cursorPosition: action.cursorPosition + }) + }; +} + +function handleSetPostDraft(state, action) { + return { + ...state, + [action.channelId]: { + draft: action.draft, + cursorPosition: 0, + files: action.files + } + }; +} + +function handleSelectChannel(state, action) { + let data = {...state}; + if (action.data && !data[action.data]) { + data = { ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {draft: action.postDraft}) - }; - } - case ViewTypes.SET_POST_DRAFT: { - return { - ...state, - [action.channelId]: { - draft: action.postDraft, - files: action.files + [action.data]: { + draft: '', + cursorPosition: 0, + files: [] } }; } - case ChannelTypes.SELECT_CHANNEL: { - let data = {...state}; - if (action.data && !data[action.data]) { - data = { - ...state, - [action.data]: { - draft: '', - files: [] - } + + return data; +} + +function handleSetTempUploadFileForPostDraft(state, action) { + if (action.rootId) { + return state; + } + + const tempFiles = action.clientIds.map((temp) => ({...temp, loading: true})); + const files = [ + ...state[action.channelId].files, + ...tempFiles + ]; + + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {files}) + }; +} + +function handleRetryUploadFileForPost(state, action) { + if (action.rootId) { + return state; + } + + const files = state[action.channelId].files.map((f) => { + if (f.clientId === action.clientId) { + return { + ...f, + loading: true, + failed: false }; } - return data; + return f; + }); + + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {files}) + }; +} + +function handleReceivedUploadFiles(state, action) { + if (action.rootId || !state[action.channelId].files) { + return state; } - case ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT: { - if (action.rootId) { - return state; + + // Reconcile tempFiles with the received uploaded files + const files = state[action.channelId].files.map((tempFile) => { + const file = action.data.find((f) => f.clientId === tempFile.clientId); + if (file) { + return { + ...file, + localPath: tempFile.localPath + }; } - const tempFiles = action.clientIds.map((temp) => ({...temp, loading: true})); - const files = [ - ...state[action.channelId].files, - ...tempFiles - ]; + return tempFile; + }); - return { - ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {files}) - }; + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {files}) + }; +} + +function handleUploadFilesFailure(state, action) { + if (action.rootId) { + return state; } - case ViewTypes.RETRY_UPLOAD_FILE_FOR_POST: { - if (action.rootId) { - return state; + + const clientIds = action.clientIds; + const files = state[action.channelId].files.map((tempFile) => { + if (clientIds.includes(tempFile.clientId)) { + return { + ...tempFile, + loading: false, + failed: true + }; } - const files = state[action.channelId].files.map((f) => { - if (f.clientId === action.clientId) { - return { - ...f, - loading: true, - failed: false - }; - } + return tempFile; + }); - return f; - }); + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {files}) + }; +} - return { - ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {files}) - }; +function handleClearFilesForPostDraft(state, action) { + if (action.rootId) { + return state; } - case FileTypes.RECEIVED_UPLOAD_FILES: { - if (action.rootId || !state[action.channelId].files) { - return state; - } - // Reconcile tempFiles with the received uploaded files - const files = state[action.channelId].files.map((tempFile) => { - const file = action.data.find((f) => f.clientId === tempFile.clientId); - if (file) { - return { - ...file, - localPath: tempFile.localPath - }; - } + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {files: []}) + }; +} - return tempFile; - }); - - return { - ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {files}) - }; +function handleRemoveFileFromPostDraft(state, action) { + if (action.rootId) { + return state; } - case FileTypes.UPLOAD_FILES_FAILURE: { - if (action.rootId) { - return state; - } - const clientIds = action.clientIds; - const files = state[action.channelId].files.map((tempFile) => { - if (clientIds.includes(tempFile.clientId)) { - return { - ...tempFile, - loading: false, - failed: true - }; - } + const files = state[action.channelId].files.filter((file) => (file.clientId !== action.clientId)); - return tempFile; - }); + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {files}) + }; +} - return { - ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {files}) - }; +function handleRemoveLastFileFromPostDraft(state, action) { + if (action.rootId) { + return state; } - case ViewTypes.CLEAR_FILES_FOR_POST_DRAFT: { - if (action.rootId) { - return state; - } - return { - ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {files: []}) - }; - } - case ViewTypes.REMOVE_FILE_FROM_POST_DRAFT: { - if (action.rootId) { - return state; - } + const files = [...state[action.channelId].files]; + files.splice(-1); - const files = state[action.channelId].files.filter((file) => (file.clientId !== action.clientId)); + return { + ...state, + [action.channelId]: Object.assign({}, state[action.channelId], {files}) + }; +} - return { - ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {files}) - }; - } - case ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT: { - if (action.rootId) { - return state; - } - - const files = [...state[action.channelId].files]; - files.splice(-1); - - return { - ...state, - [action.channelId]: Object.assign({}, state[action.channelId], {files}) - }; - } +function drafts(state = {}, action) { // eslint-disable-line complexity + switch (action.type) { + case ViewTypes.POST_DRAFT_CHANGED: + return handlePostDraftChanged(state, action); + case ViewTypes.POST_DRAFT_SELECTION_CHANGED: + return handlePostDraftSelectionChanged(state, action); + case ViewTypes.SET_POST_DRAFT: + return handleSetPostDraft(state, action); + case ChannelTypes.SELECT_CHANNEL: + return handleSelectChannel(state, action); + case ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT: + return handleSetTempUploadFileForPostDraft(state, action); + case ViewTypes.RETRY_UPLOAD_FILE_FOR_POST: + return handleRetryUploadFileForPost(state, action); + case FileTypes.RECEIVED_UPLOAD_FILES: + return handleReceivedUploadFiles(state, action); + case FileTypes.UPLOAD_FILES_FAILURE: + return handleUploadFilesFailure(state, action); + case ViewTypes.CLEAR_FILES_FOR_POST_DRAFT: + return handleClearFilesForPostDraft(state, action); + case ViewTypes.REMOVE_FILE_FROM_POST_DRAFT: + return handleRemoveFileFromPostDraft(state, action); + case ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT: + return handleRemoveLastFileFromPostDraft(state, action); default: return state; } diff --git a/app/reducers/views/thread.js b/app/reducers/views/thread.js index 10ca589e6..489e2d9f8 100644 --- a/app/reducers/views/thread.js +++ b/app/reducers/views/thread.js @@ -6,155 +6,216 @@ import {FileTypes, PostTypes} from 'mattermost-redux/action_types'; import {ViewTypes} from 'app/constants'; -function drafts(state = {}, action) { - switch (action.type) { - case ViewTypes.COMMENT_DRAFT_CHANGED: - return { +function handleCommentDraftChanged(state, action) { + if (!action.rootId) { + return state; + } + + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {draft: action.draft}) + }; +} + +function handleSetCommentDraft(state, action) { + if (!action.rootId) { + return state; + } + + return { + ...state, + [action.rootId]: { + draft: action.draft, + cursorPosition: 0, + files: action.files + } + }; +} + +function handleCommentDraftSelectionChange(state, action) { + if (!action.rootId) { + return state; + } + + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], { + cursorPosition: action.cursorPosition + }) + }; +} + +function handleReceivedPostSelected(state, action) { + if (!action.data) { + return state; + } + + let data = {...state}; + + if (!data[action.data]) { + data = { ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {draft: action.draft}) - }; - case ViewTypes.SET_COMMENT_DRAFT: - return { - ...state, - [action.rootId]: { - draft: action.draft, - files: action.files + [action.data]: { + draft: '', + cursorPosition: 0, + files: [] } }; - case PostTypes.RECEIVED_POST_SELECTED: { - let data = {...state}; + } - if (!data[action.data]) { - data = { - ...state, - [action.data]: { - draft: '', - files: [] - } + return data; +} + +function handleSetTempUploadFilesForPostDraft(state, action) { + if (!action.rootId) { + return state; + } + + const tempFiles = action.clientIds.map((temp) => ({...temp, loading: true})); + const files = [ + ...state[action.rootId].files, + ...tempFiles + ]; + + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {files}) + }; +} + +function handleRetryUploadForPost(state, action) { + if (!action.rootId) { + return state; + } + + const files = state[action.rootId].files.map((f) => { + if (f.clientId === action.clientId) { + return { + ...f, + loading: true, + failed: false }; } - return data; + return f; + }); + + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {files}) + }; +} + +function handleReceiveUploadFiles(state, action) { + if (!action.rootId) { + return state; } - case ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT: { - if (!action.rootId) { - return state; + + // Reconcile tempFiles with the received uploaded files + const files = state[action.rootId].files.map((tempFile) => { + const file = action.data.find((f) => f.clientId === tempFile.clientId); + if (file) { + return { + ...file, + localPath: tempFile.localPath + }; } - const tempFiles = action.clientIds.map((temp) => ({...temp, loading: true})); - const files = [ - ...state[action.rootId].files, - ...tempFiles - ]; + return tempFile; + }); - return { - ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {files}) - }; + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {files}) + }; +} + +function handleUploadFilesFailure(state, action) { + if (!action.rootId) { + return state; } - case ViewTypes.RETRY_UPLOAD_FILE_FOR_POST: { - if (!action.rootId) { - return state; + + const clientIds = action.clientIds; + const files = state[action.rootId].files.map((tempFile) => { + if (clientIds.includes(tempFile.clientId)) { + return { + ...tempFile, + loading: false, + failed: true + }; } - const files = state[action.rootId].files.map((f) => { - if (f.clientId === action.clientId) { - return { - ...f, - loading: true, - failed: false - }; - } + return tempFile; + }); - return f; - }); + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {files}) + }; +} - return { - ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {files}) - }; +function handleClearFilesForPostDraft(state, action) { + if (!action.rootId) { + return state; } - case FileTypes.RECEIVED_UPLOAD_FILES: { - if (!action.rootId) { - return state; - } - // Reconcile tempFiles with the received uploaded files - const files = state[action.rootId].files.map((tempFile) => { - const file = action.data.find((f) => f.clientId === tempFile.clientId); - if (file) { - return { - ...file, - localPath: tempFile.localPath - }; - } + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {files: []}) + }; +} - return tempFile; - }); - - return { - ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {files}) - }; +function handleRemoveFileFromPostDraft(state, action) { + if (!action.rootId) { + return state; } - case FileTypes.UPLOAD_FILES_FAILURE: { - if (!action.rootId) { - return state; - } - const clientIds = action.clientIds; - const files = state[action.rootId].files.map((tempFile) => { - if (clientIds.includes(tempFile.clientId)) { - return { - ...tempFile, - loading: false, - failed: true - }; - } + const files = state[action.rootId].files.filter((file) => (file.clientId !== action.clientId)); - return tempFile; - }); + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {files}) + }; +} - return { - ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {files}) - }; +function handleRemoveLastFromPostDraft(state, action) { + if (!action.rootId) { + return state; } - case ViewTypes.CLEAR_FILES_FOR_POST_DRAFT: { - if (!action.rootId) { - return state; - } - return { - ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {files: []}) - }; - } - case ViewTypes.REMOVE_FILE_FROM_POST_DRAFT: { - if (!action.rootId) { - return state; - } + const files = [...state[action.rootId].files]; + files.splice(-1); - const files = state[action.rootId].files.filter((file) => (file.clientId !== action.clientId)); + return { + ...state, + [action.rootId]: Object.assign({}, state[action.rootId], {files}) + }; +} - return { - ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {files}) - }; - } - case ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT: { - if (!action.rootId) { - return state; - } - - const files = [...state[action.rootId].files]; - files.splice(-1); - - return { - ...state, - [action.rootId]: Object.assign({}, state[action.rootId], {files}) - }; - } +function drafts(state = {}, action) { // eslint-disable-line complexity + switch (action.type) { + case ViewTypes.COMMENT_DRAFT_CHANGED: + return handleCommentDraftChanged(state, action); + case ViewTypes.SET_COMMENT_DRAFT: + return handleSetCommentDraft(state, action); + case ViewTypes.COMMENT_DRAFT_SELECTION_CHANGED: + return handleCommentDraftSelectionChange(state, action); + case PostTypes.RECEIVED_POST_SELECTED: + return handleReceivedPostSelected(state, action); + case ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT: + return handleSetTempUploadFilesForPostDraft(state, action); + case ViewTypes.RETRY_UPLOAD_FILE_FOR_POST: + return handleRetryUploadForPost(state, action); + case FileTypes.RECEIVED_UPLOAD_FILES: + return handleReceiveUploadFiles(state, action); + case FileTypes.UPLOAD_FILES_FAILURE: + return handleUploadFilesFailure(state, action); + case ViewTypes.CLEAR_FILES_FOR_POST_DRAFT: + return handleClearFilesForPostDraft(state, action); + case ViewTypes.REMOVE_FILE_FROM_POST_DRAFT: + return handleRemoveFileFromPostDraft(state, action); + case ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT: + return handleRemoveLastFromPostDraft(state, action); default: return state; } diff --git a/app/screens/code/index.js b/app/screens/code/index.js index cbd69d8de..367c1b27f 100644 --- a/app/screens/code/index.js +++ b/app/screens/code/index.js @@ -13,4 +13,4 @@ function mapStateToProps(state) { }; } -export default connect(mapStateToProps)(Code); +export default connect(mapStateToProps)(Code); \ No newline at end of file diff --git a/assets/base/config.json b/assets/base/config.json index 0bcd6b67b..e97d7931f 100644 --- a/assets/base/config.json +++ b/assets/base/config.json @@ -11,6 +11,7 @@ "PlatformNoticeURL": "https://about.mattermost.com/platform-notice-txt/", "MobileNoticeURL": "https://about.mattermost.com/mobile-notice-txt/", "SegmentApiKey": "3MT7rAoC0OP7yy3ThzqFSAtKzmzqtUPX", + "ExperimentalUsernamePressIsMention": false, "SentryEnabled": false, "SentryDsnIos": "",