From b318a69fc4d1ed7bad068bf0f2b8fd33990f67ab Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 22 Mar 2021 15:30:50 -0700 Subject: [PATCH 01/28] Edit Comments - fundamental API portions, version allows to edit to a hardcoded comment in code --- src/ONYXKEYS.js | 1 + src/components/PopoverWithMeasuredContent.js | 2 +- src/libs/API.js | 14 +++ src/libs/actions/Report.js | 19 +++ .../home/report/ReportActionContextMenu.js | 110 ++++++++++-------- .../report/ReportActionContextMenuItem.js | 12 +- src/pages/home/report/ReportActionItem.js | 16 +-- 7 files changed, 110 insertions(+), 64 deletions(-) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 8edd302fb8c6..58b066c034d7 100644 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -60,6 +60,7 @@ export default { REPORT: 'report_', REPORT_ACTIONS: 'reportActions_', REPORT_DRAFT_COMMENT: 'reportDraftComment_', + REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_', REPORT_USER_IS_TYPING: 'reportUserIsTyping_', }, }; diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 03c0ec0553e7..71bb3841025e 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -137,7 +137,7 @@ class PopoverWithMeasuredContent extends Component { but we can't measure its dimensions without first rendering it. */ - {this.props.measureContent()} + {this.props.children} ); } diff --git a/src/libs/API.js b/src/libs/API.js index 852dee8497d8..e513e03e5c46 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -515,6 +515,19 @@ function Report_TogglePinned(parameters) { return Network.post(commandName, parameters); } +/** + * @param {Object} parameters + * @param {Number} parameters.reportID + * @param {String} parameters.reportActionID + * @param {String} parameters.reportComment + * @return {Promise} + */ +function Report_EditComment(parameters) { + const commandName = 'Report_EditComment'; + requireParameters(['reportID', 'reportActionID', 'reportComment'], parameters, commandName); + return Network.post(commandName, parameters); +} + /** * @param {Object} parameters * @param {Number} parameters.accountID @@ -640,6 +653,7 @@ export { Report_AddComment, Report_GetHistory, Report_TogglePinned, + Report_EditComment, Report_UpdateLastRead, ResendValidateCode, SetNameValuePair, diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 38e5b25bfcd5..f5cf8d5afedd 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -764,6 +764,24 @@ NetworkConnection.onReconnect(() => { fetchAll(false); }); +function editReportComment(reportID, reportAction, newMessage) { + // // Optimistically update the report action with the new message + // reportAction.message.isEdited = true; + // reportAction.message.html = newMessage; + // Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {reportAction}); + + // Persist the updated report comment + API.Report_EditComment({ + reportID, + reportActionID: reportAction.reportActionID, + reportComment: newMessage, + }); +} + +function saveReportActionDraft(reportID, reportActionID, draftMessage) { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}_${reportActionID}`, draftMessage); +} + export { fetchAll, fetchActions, @@ -777,4 +795,5 @@ export { broadcastUserIsTyping, togglePinnedState, updateCurrentlyViewedReportID, + editReportComment, }; diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index 80156f9443e3..82b1581f9993 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -6,41 +6,8 @@ import { } from '../../../components/Icon/Expensicons'; import getReportActionContextMenuStyles from '../../../styles/getReportActionContextMenuStyles'; import ReportActionContextMenuItem from './ReportActionContextMenuItem'; - -/** - * A list of all the context actions in this menu. - */ -const CONTEXT_ACTIONS = [ - // Copy to clipboard - { - text: 'Copy to Clipboard', - icon: Clipboard, - }, - - // Copy chat link - { - text: 'Copy Link', - icon: LinkCopy, - }, - - // Mark as Unread - { - text: 'Mark as Unread', - icon: Mail, - }, - - // Edit Comment - { - text: 'Edit Comment', - icon: Pencil, - }, - - // Delete Comment - { - text: 'Delete Comment', - icon: Trashcan, - }, -]; +import {editReportComment} from '../../../libs/actions/Report'; +import ReportActionPropTypes from './ReportActionPropTypes'; const propTypes = { // The ID of the report this report action is attached to. @@ -49,7 +16,7 @@ const propTypes = { // The ID of the report action this context menu is attached to. // eslint-disable-next-line react/no-unused-prop-types - reportActionID: PropTypes.number.isRequired, + reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, // If true, this component will be a small, row-oriented menu that displays icons but not text. // If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. @@ -64,21 +31,62 @@ const defaultProps = { isVisible: false, }; -const ReportActionContextMenu = (props) => { - const wrapperStyle = getReportActionContextMenuStyles(props.isMini); - return props.isVisible && ( - - {CONTEXT_ACTIONS.map(contextAction => ( - - ))} - - ); -}; +class ReportActionContextMenu extends React.Component { + /** + * A list of all the context actions in this menu. + */ + CONTEXT_ACTIONS = [ + // Copy to clipboard + { + text: 'Copy to Clipboard', + icon: Clipboard, + }, + + // Copy chat link + { + text: 'Copy Link', + icon: LinkCopy, + }, + + // Mark as Unread + { + text: 'Mark as Unread', + icon: Mail, + }, + + // Edit Comment + { + text: 'Edit Comment', + icon: Pencil, + callback: () => { + editReportComment(this.props.reportID, this.props.reportAction, "blah blah Yuwen test 4"); + }, + }, + + // Delete Comment + { + text: 'Delete Comment', + icon: Trashcan, + }, + ]; + + render() { + const wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); + return this.props.isVisible && ( + + {this.CONTEXT_ACTIONS.map(contextAction => ( + + ))} + + ); + } +} ReportActionContextMenu.propTypes = propTypes; ReportActionContextMenu.defaultProps = defaultProps; diff --git a/src/pages/home/report/ReportActionContextMenuItem.js b/src/pages/home/report/ReportActionContextMenuItem.js index 6b60ce74d9f5..59de57a1ef40 100644 --- a/src/pages/home/report/ReportActionContextMenuItem.js +++ b/src/pages/home/report/ReportActionContextMenuItem.js @@ -30,10 +30,12 @@ const propTypes = { icon: PropTypes.elementType.isRequired, text: PropTypes.string.isRequired, isMini: PropTypes.bool, + onPressOut: PropTypes.func, }; const defaultProps = { isMini: false, + onPressOut: () => {}, }; const ReportActionContextMenuItem = (props) => { @@ -42,7 +44,10 @@ const ReportActionContextMenuItem = (props) => { props.isMini ? ( - getButtonStyle(getButtonState(hovered, pressed))}> + getButtonStyle(getButtonState(hovered, pressed))} + onPressOut={props.onPressOut} + > {({hovered, pressed}) => ( { ) : ( - getButtonStyle(getButtonState(hovered, pressed))}> + getButtonStyle(getButtonState(hovered, pressed))} + onPressOut={props.onPressOut} + > {({hovered, pressed}) => ( <> ( - - )} > From 41e885dcaf50603ad21ef143b541c438fa40341e Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 23 Mar 2021 14:13:12 -0700 Subject: [PATCH 02/28] Get dirty editing UI in place --- src/components/PopoverWithMeasuredContent.js | 4 - src/libs/actions/Report.js | 21 ++++-- .../home/report/ReportActionContextMenu.js | 5 +- src/pages/home/report/ReportActionItem.js | 9 ++- src/pages/home/report/ReportActionItemEdit.js | 75 +++++++++++++++++++ src/styles/styles.js | 10 +++ 6 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 src/pages/home/report/ReportActionItemEdit.js diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 71bb3841025e..7ef851912507 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -25,10 +25,6 @@ const propTypes = { horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), - - // A function with content to measure. This component will use this.props.children by default, - // but in the case the children are not displayed, the measurement will not work. - measureContent: PropTypes.func.isRequired, }; const defaultProps = { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index f5cf8d5afedd..fa222c6cbe1b 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -764,22 +764,28 @@ NetworkConnection.onReconnect(() => { fetchAll(false); }); -function editReportComment(reportID, reportAction, newMessage) { - // // Optimistically update the report action with the new message - // reportAction.message.isEdited = true; - // reportAction.message.html = newMessage; - // Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {reportAction}); +function editReportComment(reportID, reportAction, htmlForNewComment) { + debugger; + // Optimistically update the report action with the new message + const sequenceNumber = reportAction.sequenceNumber; + const newReportAction = {...reportAction}; + const actionToMerge = {}; + newReportAction.message[0].isEdited = true; + newReportAction.message[0].html = htmlForNewComment; + newReportAction.message[0].text = htmlForNewComment.replace(/<[^>]*>?/gm, ''); + actionToMerge[sequenceNumber] = newReportAction; + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge); // Persist the updated report comment API.Report_EditComment({ reportID, reportActionID: reportAction.reportActionID, - reportComment: newMessage, + reportComment: htmlForNewComment, }); } function saveReportActionDraft(reportID, reportActionID, draftMessage) { - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}_${reportActionID}`, draftMessage); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${reportActionID}`, draftMessage); } export { @@ -796,4 +802,5 @@ export { togglePinnedState, updateCurrentlyViewedReportID, editReportComment, + saveReportActionDraft, }; diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index 82b1581f9993..95e322b89072 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -6,7 +6,7 @@ import { } from '../../../components/Icon/Expensicons'; import getReportActionContextMenuStyles from '../../../styles/getReportActionContextMenuStyles'; import ReportActionContextMenuItem from './ReportActionContextMenuItem'; -import {editReportComment} from '../../../libs/actions/Report'; +import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; import ReportActionPropTypes from './ReportActionPropTypes'; const propTypes = { @@ -59,7 +59,8 @@ class ReportActionContextMenu extends React.Component { text: 'Edit Comment', icon: Pencil, callback: () => { - editReportComment(this.props.reportID, this.props.reportAction, "blah blah Yuwen test 4"); + // editReportComment(this.props.reportID, this.props.reportAction, "blah blah Yuwen test 21"); + saveReportActionDraft(this.props.reportID, this.props.reportAction.reportActionID, "blah blah Yuwen test 21"); }, }, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 14b0c4597a1b..4fc326f45468 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -14,6 +14,7 @@ import PopoverWithMeasuredContent from '../../../components/PopoverWithMeasuredC import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemGrouped from './ReportActionItemGrouped'; import ReportActionContextMenu from './ReportActionContextMenu'; +import ReportActionItemEdit from './ReportActionItemEdit'; const propTypes = { // The ID of the report this action is on. @@ -29,6 +30,7 @@ const propTypes = { // List of betas for the current user. betas: PropTypes.arrayOf(PropTypes.string), + // Draft message - if this is set the comment is in 'edit' mode draftMessage: PropTypes.string, }; @@ -105,7 +107,9 @@ class ReportActionItem extends Component { } render() { - return ( + return this.props.draftMessage ? ( + + ) : ( {hovered => ( @@ -154,4 +158,7 @@ export default withOnyx({ betas: { key: ONYXKEYS.BETAS, }, + draftMessage: { + key: ({reportID, action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${action.reportActionID}`, + }, })(ReportActionItem); diff --git a/src/pages/home/report/ReportActionItemEdit.js b/src/pages/home/report/ReportActionItemEdit.js new file mode 100644 index 000000000000..efc33e147b0a --- /dev/null +++ b/src/pages/home/report/ReportActionItemEdit.js @@ -0,0 +1,75 @@ +import React from 'react'; +import {View, Pressable, Text} from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import ReportActionPropTypes from './ReportActionPropTypes'; +import ReportActionItemFragment from './ReportActionItemFragment'; +import styles from '../../../styles/styles'; +import CONST from '../../../CONST'; +import ReportActionItemDate from './ReportActionItemDate'; +import Avatar from '../../../components/Avatar'; +import TextInputFocusable from '../../../components/TextInputFocusable'; + +const propTypes = { + // All the data of the action + action: PropTypes.shape(ReportActionPropTypes).isRequired, + + // Draft message + draftMessage: PropTypes.string.isRequired, +}; + +class ReportActionItemEdit extends React.Component { + constructor(props) { + super(props); + } + + render() { + const avatarUrl = this.props.action.automatic + ? `${CONST.CLOUDFRONT_URL}/images/icons/concierge_2019.svg` + : this.props.action.avatar; + return ( + + + + + {_.map(this.props.action.person, (fragment, index) => ( + + ))} + + + this.textInput = el} + onChangeText={() => {}} // Debounced saveDraftComment + defaultValue={this.props.draftMessage} + maxLines={16} // This is the same that slack has + style={[styles.textInputCompose, styles.flex4]} + /> + + + + + Cancel + + + + + Save + + + + + ); + } +} + +ReportActionItemEdit.propTypes = propTypes; +export default ReportActionItemEdit; diff --git a/src/styles/styles.js b/src/styles/styles.js index 28e0130bc503..33fb05ff1706 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -698,6 +698,16 @@ const styles = { display: 'flex', }, + chatItemEdit: { + backgroundColor: themeColors.buttonSuccessHoveredBG, + display: 'flex', + flexDirection: 'row', + paddingTop: 8, + paddingBottom: 8, + paddingLeft: 20, + paddingRight: 20, + }, + chatItemComposeBoxColor: { borderColor: themeColors.border, }, From 94053ea0265b1b968b39d52d0ba5387a5faa9f38 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Thu, 8 Apr 2021 22:06:59 -0700 Subject: [PATCH 03/28] reall fix conflicts --- .../home/report/ReportActionContextMenu.js | 187 ++++++++++-------- 1 file changed, 102 insertions(+), 85 deletions(-) diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index eed8de4b87f9..b2f6ec0e3c5c 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -3,6 +3,7 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; +import {withOnyx} from 'react-native-onyx'; import { Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, } from '../../../components/Icon/Expensicons'; @@ -12,72 +13,7 @@ import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Re import ReportActionPropTypes from './ReportActionPropTypes'; import Clipboard from '../../../libs/Clipboard'; import {isReportMessageAttachment} from '../../../libs/reportUtils'; - -/** - * A list of all the context actions in this menu. - */ -const CONTEXT_ACTIONS = [ - // Copy to clipboard - { - text: 'Copy to Clipboard', - icon: ClipboardIcon, - successText: 'Copied!', - successIcon: Checkmark, - shouldShow: true, - - // If return value is true, we switch the `text` and `icon` on - // `ReportActionContextMenuItem` with `successText` and `successIcon` which will fallback to - // the `text` and `icon` - onPress: (action) => { - const message = _.last(lodashGet(action, 'message', null)); - const html = lodashGet(message, 'html', ''); - const text = lodashGet(message, 'text', ''); - const isAttachment = _.has(action, 'isAttachment') - ? action.isAttachment - : isReportMessageAttachment(text); - if (!isAttachment) { - Clipboard.setString(text); - } else { - Clipboard.setString(html); - } - }, - }, - - // Copy chat link - { - text: 'Copy Link', - icon: LinkCopy, - shouldShow: false, - onPress: () => {}, - }, - - // Mark as Unread - { - text: 'Mark as Unread', - icon: Mail, - shouldShow: false, - onPress: () => {}, - }, - - // Edit Comment - { - text: 'Edit Comment', - icon: Pencil, - shouldShow: false, - onPress: () => { - editReportComment(this.props.reportID, this.props.reportAction, "blah blah Yuwen test 21"); - saveReportActionDraft(this.props.reportID, this.props.reportAction.reportActionID, "blah blah Yuwen test 21"); - }, - }, - - // Delete Comment - { - text: 'Delete Comment', - icon: Trashcan, - shouldShow: false, - onPress: () => {}, - }, -]; +import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { // The ID of the report this report action is attached to. @@ -93,33 +29,114 @@ const propTypes = { // Controls the visibility of this component. isVisible: PropTypes.bool, + + /* Onyx Props */ + // The session of the logged in person + session: PropTypes.shape({ + // Email of the logged in person + email: PropTypes.string, + }), }; const defaultProps = { isMini: false, isVisible: false, + session: {}, }; -const ReportActionContextMenu = (props) => { - const wrapperStyle = getReportActionContextMenuStyles(props.isMini); - return props.isVisible && ( - - {CONTEXT_ACTIONS.map(contextAction => contextAction.shouldShow && ( - contextAction.onPress(props.reportAction)} - key={contextAction.text} - /> - ))} - - ); -}; +class ReportActionContextMenu extends React.Component { + /** + * A list of all the context actions in this menu. + */ + CONTEXT_ACTIONS = [ + // Copy to clipboard + { + text: 'Copy to Clipboard', + icon: ClipboardIcon, + successText: 'Copied!', + successIcon: Checkmark, + shouldShow: true, + + // If return value is true, we switch the `text` and `icon` on + // `ReportActionContextMenuItem` with `successText` and `successIcon` which will fallback to + // the `text` and `icon` + onPress: (action) => { + const message = _.last(lodashGet(action, 'message', null)); + const html = lodashGet(message, 'html', ''); + const text = lodashGet(message, 'text', ''); + const isAttachment = _.has(action, 'isAttachment') + ? action.isAttachment + : isReportMessageAttachment(text); + if (!isAttachment) { + Clipboard.setString(text); + } else { + Clipboard.setString(html); + } + }, + }, + + // Copy chat link + { + text: 'Copy Link', + icon: LinkCopy, + shouldShow: false, + onPress: () => {}, + }, + + // Mark as Unread + { + text: 'Mark as Unread', + icon: Mail, + shouldShow: false, + onPress: () => {}, + }, + + // Edit Comment + { + text: 'Edit Comment', + icon: Pencil, + shouldShow: this.props.reportAction.actorEmail === this.props.session.email, + onPress: () => { + editReportComment(this.props.reportID, this.props.reportAction, "blah blah Yuwen test 21"); + saveReportActionDraft(this.props.reportID, this.props.reportAction.reportActionID, "blah blah Yuwen test 21"); + }, + }, + + // Delete Comment + { + text: 'Delete Comment', + icon: Trashcan, + shouldShow: false, + onPress: () => {}, + }, + ]; + + + render() { + const wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); + return this.props.isVisible && ( + + {this.CONTEXT_ACTIONS.map(contextAction => contextAction.shouldShow && ( + contextAction.onPress(this.props.reportAction)} + key={contextAction.text} + /> + ))} + + ); + } +} ReportActionContextMenu.propTypes = propTypes; ReportActionContextMenu.defaultProps = defaultProps; -export default ReportActionContextMenu; +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, +})(ReportActionContextMenu); From bf1e59c4f160ae1452858e10da82b535b06344b1 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Fri, 9 Apr 2021 00:08:21 -0700 Subject: [PATCH 04/28] Really non-performant comment editing --- src/libs/actions/Report.js | 2 +- .../home/report/ReportActionContextMenu.js | 12 ++- src/pages/home/report/ReportActionItem.js | 9 +- src/pages/home/report/ReportActionItemEdit.js | 75 ---------------- .../home/report/ReportActionItemGrouped.js | 12 ++- .../report/ReportActionItemMessageEdit.js | 85 +++++++++++++++++++ .../home/report/ReportActionItemSingle.js | 11 ++- 7 files changed, 116 insertions(+), 90 deletions(-) delete mode 100644 src/pages/home/report/ReportActionItemEdit.js create mode 100644 src/pages/home/report/ReportActionItemMessageEdit.js diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 7499e092fc2a..e47404b0e5c0 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -917,7 +917,7 @@ function editReportComment(reportID, reportAction, htmlForNewComment) { } function saveReportActionDraft(reportID, reportActionID, draftMessage) { - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${reportActionID}`, draftMessage); + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${reportActionID}`, draftMessage); } export { diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index b2f6ec0e3c5c..09b4e03c657e 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -9,7 +9,7 @@ import { } from '../../../components/Icon/Expensicons'; import getReportActionContextMenuStyles from '../../../styles/getReportActionContextMenuStyles'; import ReportActionContextMenuItem from './ReportActionContextMenuItem'; -import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; +import {saveReportActionDraft} from '../../../libs/actions/Report'; import ReportActionPropTypes from './ReportActionPropTypes'; import Clipboard from '../../../libs/Clipboard'; import {isReportMessageAttachment} from '../../../libs/reportUtils'; @@ -95,10 +95,10 @@ class ReportActionContextMenu extends React.Component { { text: 'Edit Comment', icon: Pencil, - shouldShow: this.props.reportAction.actorEmail === this.props.session.email, + shouldShow: this.props.reportAction.actorEmail === this.props.session.email + && !isReportMessageAttachment(this.getActionText()), onPress: () => { - editReportComment(this.props.reportID, this.props.reportAction, "blah blah Yuwen test 21"); - saveReportActionDraft(this.props.reportID, this.props.reportAction.reportActionID, "blah blah Yuwen test 21"); + saveReportActionDraft(this.props.reportID, this.props.reportAction.reportActionID, this.getActionText()); }, }, @@ -111,6 +111,10 @@ class ReportActionContextMenu extends React.Component { }, ]; + getActionText() { + const message = _.last(lodashGet(this.props.reportAction, 'message', null)); + return lodashGet(message, 'text', ''); + } render() { const wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 926e088e1023..a28786bfa56c 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -15,7 +15,6 @@ import PopoverWithMeasuredContent from '../../../components/PopoverWithMeasuredC import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemGrouped from './ReportActionItemGrouped'; import ReportActionContextMenu from './ReportActionContextMenu'; -import ReportActionItemEdit from './ReportActionItemEdit'; const propTypes = { // The ID of the report this action is on. @@ -91,17 +90,15 @@ class ReportActionItem extends Component { } render() { - return this.props.draftMessage ? ( - - ) : ( + return ( {hovered => ( {!this.props.displayAsGroup - ? - : } + ? + : } - - - - {_.map(this.props.action.person, (fragment, index) => ( - - ))} - - - this.textInput = el} - onChangeText={() => {}} // Debounced saveDraftComment - defaultValue={this.props.draftMessage} - maxLines={16} // This is the same that slack has - style={[styles.textInputCompose, styles.flex4]} - /> - - - - - Cancel - - - - - Save - - - - - ); - } -} - -ReportActionItemEdit.propTypes = propTypes; -export default ReportActionItemEdit; diff --git a/src/pages/home/report/ReportActionItemGrouped.js b/src/pages/home/report/ReportActionItemGrouped.js index 616b3f103c99..c6c90691a438 100644 --- a/src/pages/home/report/ReportActionItemGrouped.js +++ b/src/pages/home/report/ReportActionItemGrouped.js @@ -1,19 +1,27 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; +import _ from 'underscore'; import ReportActionPropTypes from './ReportActionPropTypes'; import ReportActionItemMessage from './ReportActionItemMessage'; import styles from '../../../styles/styles'; +import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; const propTypes = { // All the data of the action action: PropTypes.shape(ReportActionPropTypes).isRequired, + + draftMessage: PropTypes.string.isRequired, + + reportID: PropTypes.number.isRequired, }; -const ReportActionItemGrouped = ({action}) => ( +const ReportActionItemGrouped = ({action, draftMessage}) => ( - + {_.isEmpty(draftMessage) + ? + : } ); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js new file mode 100644 index 000000000000..a6c60d56d3b9 --- /dev/null +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -0,0 +1,85 @@ +import React from 'react'; +import {View, Pressable, Text} from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import ReportActionPropTypes from './ReportActionPropTypes'; +import styles from '../../../styles/styles'; +import TextInputFocusable from '../../../components/TextInputFocusable'; +import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; + +const propTypes = { + // All the data of the action + action: PropTypes.shape(ReportActionPropTypes).isRequired, + + // Draft message + draftMessage: PropTypes.string.isRequired, + + reportID: PropTypes.number.isRequired, +}; + +class ReportActionItemMessageEdit extends React.Component { + constructor(props) { + super(props); + this.updateDraft = this.updateDraft.bind(this); + this.deleteDraft = this.deleteDraft.bind(this); + this.debouncedSaveDraft = _.debounce(this.debouncedSaveDraft.bind(this), 1000, false); + this.publishDraft = this.publishDraft.bind(this); + + this.state = { + draft: this.props.draftMessage, + }; + } + + /** + * Update the value of the draft in Onyx + * + * @param {String} newDraft + */ + updateDraft(newDraft) { + this.setState({draft: newDraft}); + this.debouncedSaveDraft(newDraft); + } + + deleteDraft() { + saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, ''); + } + + debouncedSaveDraft() { + saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, this.state.draft); + } + + publishDraft() { + editReportComment(this.props.reportID, this.props.action, this.state.draft); + this.deleteDraft(); + } + + render() { + return ( + + this.textInput = el} + onChangeText={this.updateDraft} // Debounced saveDraftComment + defaultValue={this.props.draftMessage} + maxLines={16} // This is the same that slack has + style={[styles.textInputCompose, styles.flex4]} + /> + + + + Cancel + + + + + Save + + + + + ); + } +} + +ReportActionItemMessageEdit.propTypes = propTypes; +export default ReportActionItemMessageEdit; diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index e9cc33f9d085..0bc936886dff 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -12,6 +12,7 @@ import ReportActionItemDate from './ReportActionItemDate'; import Avatar from '../../../components/Avatar'; import ONYXKEYS from '../../../ONYXKEYS'; import personalDetailsPropType from '../../personalDetailsPropType'; +import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; const propTypes = { // All the data of the action @@ -19,9 +20,13 @@ const propTypes = { // All of the personalDetails personalDetails: PropTypes.objectOf(personalDetailsPropType).isRequired, + + draftMessage: PropTypes.string.isRequired, + + reportID: PropTypes.number.isRequired, }; -const ReportActionItemSingle = ({action, personalDetails}) => { +const ReportActionItemSingle = ({action, personalDetails, draftMessage, reportID}) => { const {avatar, displayName} = personalDetails[action.actorEmail] || {}; const avatarUrl = action.automatic ? `${CONST.CLOUDFRONT_URL}/images/icons/concierge_2019.svg` @@ -52,7 +57,9 @@ const ReportActionItemSingle = ({action, personalDetails}) => { ))} - + {_.isEmpty(draftMessage) + ? + : } ); From 8500513ce096bb1879b72acf2eb4cbd541677290 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 13 Apr 2021 15:10:50 -0700 Subject: [PATCH 05/28] Make sure we have reportID in grouped report action item --- src/pages/home/report/ReportActionItemGrouped.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemGrouped.js b/src/pages/home/report/ReportActionItemGrouped.js index c6c90691a438..9066cac58005 100644 --- a/src/pages/home/report/ReportActionItemGrouped.js +++ b/src/pages/home/report/ReportActionItemGrouped.js @@ -16,7 +16,7 @@ const propTypes = { reportID: PropTypes.number.isRequired, }; -const ReportActionItemGrouped = ({action, draftMessage}) => ( +const ReportActionItemGrouped = ({action, draftMessage, reportID}) => ( {_.isEmpty(draftMessage) From 8f8fd1cd5dd439448b6e688baeb3ce62f76fbc8b Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 13 Apr 2021 21:28:09 -0700 Subject: [PATCH 06/28] Fix the stylings for a message being edited --- src/libs/actions/Report.js | 1 - src/pages/home/report/ReportActionItem.js | 3 ++- .../report/ReportActionItemMessageEdit.js | 19 ++++++++++--------- src/styles/styles.js | 10 ---------- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index e47404b0e5c0..ef5f39d65bc6 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -897,7 +897,6 @@ NetworkConnection.onReconnect(() => { }); function editReportComment(reportID, reportAction, htmlForNewComment) { - debugger; // Optimistically update the report action with the new message const sequenceNumber = reportAction.sequenceNumber; const newReportAction = {...reportAction}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index a28786bfa56c..86331e5f2473 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -56,7 +56,8 @@ class ReportActionItem extends Component { shouldComponentUpdate(nextProps, nextState) { return this.state.isPopoverVisible !== nextState.isPopoverVisible || this.props.displayAsGroup !== nextProps.displayAsGroup - || !_.isEqual(this.props.action, nextProps.action); + || !_.isEqual(this.props.action, nextProps.action) + || this.props.draftMessage !== nextProps.draftMessage; } /** diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index a6c60d56d3b9..178f8eb1396e 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -6,6 +6,7 @@ import ReportActionPropTypes from './ReportActionPropTypes'; import styles from '../../../styles/styles'; import TextInputFocusable from '../../../components/TextInputFocusable'; import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; +import {TouchableOpacity} from 'react-native-web'; const propTypes = { // All the data of the action @@ -62,19 +63,19 @@ class ReportActionItemMessageEdit extends React.Component { onChangeText={this.updateDraft} // Debounced saveDraftComment defaultValue={this.props.draftMessage} maxLines={16} // This is the same that slack has - style={[styles.textInputCompose, styles.flex4]} + style={[styles.textInput, styles.flex0]} /> - - - + + + Cancel - - - - Save + + + + Save Changes - + ); diff --git a/src/styles/styles.js b/src/styles/styles.js index 0f2d833bdf67..fae27fd6d72c 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -751,16 +751,6 @@ const styles = { display: 'flex', }, - chatItemEdit: { - backgroundColor: themeColors.buttonSuccessHoveredBG, - display: 'flex', - flexDirection: 'row', - paddingTop: 8, - paddingBottom: 8, - paddingLeft: 20, - paddingRight: 20, - }, - chatItemComposeBoxColor: { borderColor: themeColors.border, }, From 00fae5d0dc081d170eb39554e53246bedbf0168a Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 13 Apr 2021 21:40:34 -0700 Subject: [PATCH 07/28] Make it so that clicking the edit comment button again cancels the edit --- src/pages/home/report/ReportActionContextMenu.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index 09b4e03c657e..dca84264af8f 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -36,6 +36,9 @@ const propTypes = { // Email of the logged in person email: PropTypes.string, }), + + // Draft message - if this is set the comment is in 'edit' mode + draftMessage: PropTypes.string, }; const defaultProps = { @@ -98,7 +101,11 @@ class ReportActionContextMenu extends React.Component { shouldShow: this.props.reportAction.actorEmail === this.props.session.email && !isReportMessageAttachment(this.getActionText()), onPress: () => { - saveReportActionDraft(this.props.reportID, this.props.reportAction.reportActionID, this.getActionText()); + saveReportActionDraft( + this.props.reportID, + this.props.reportAction.reportActionID, + _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', + ); }, }, @@ -143,4 +150,7 @@ export default withOnyx({ session: { key: ONYXKEYS.SESSION, }, + draftMessage: { + key: ({reportID, reportAction}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${reportAction.reportActionID}`, + }, })(ReportActionContextMenu); From e967c3870e75dde7ad8d8715091f0646a065a9e7 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 13 Apr 2021 22:14:19 -0700 Subject: [PATCH 08/28] Add keypress handlers --- src/pages/home/report/ReportActionItem.js | 2 +- .../home/report/ReportActionItemMessageEdit.js | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 86331e5f2473..1a00f35dbfff 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -96,7 +96,7 @@ class ReportActionItem extends Component { {hovered => ( - + {!this.props.displayAsGroup ? : } diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 178f8eb1396e..a229dea6d68e 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -25,6 +25,7 @@ class ReportActionItemMessageEdit extends React.Component { this.deleteDraft = this.deleteDraft.bind(this); this.debouncedSaveDraft = _.debounce(this.debouncedSaveDraft.bind(this), 1000, false); this.publishDraft = this.publishDraft.bind(this); + this.triggerSaveOrCancel = this.triggerSaveOrCancel.bind(this); this.state = { draft: this.props.draftMessage, @@ -37,8 +38,9 @@ class ReportActionItemMessageEdit extends React.Component { * @param {String} newDraft */ updateDraft(newDraft) { - this.setState({draft: newDraft}); - this.debouncedSaveDraft(newDraft); + const trimmedNewDraft = newDraft.trim(); + this.setState({draft: trimmedNewDraft}); + this.debouncedSaveDraft(trimmedNewDraft); } deleteDraft() { @@ -54,6 +56,16 @@ class ReportActionItemMessageEdit extends React.Component { this.deleteDraft(); } + triggerSaveOrCancel(e) { + if (e && e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + this.publishDraft(); + } else if (e && e.key === 'Escape') { + e.preventDefault(); + this.deleteDraft(); + } + } + render() { return ( @@ -61,6 +73,7 @@ class ReportActionItemMessageEdit extends React.Component { multiline ref={el => this.textInput = el} onChangeText={this.updateDraft} // Debounced saveDraftComment + onKeyPress={this.triggerSaveOrCancel} defaultValue={this.props.draftMessage} maxLines={16} // This is the same that slack has style={[styles.textInput, styles.flex0]} From cc295aec381c844d6b4c09bae2e4a2efa1ea27d9 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 13 Apr 2021 23:15:47 -0700 Subject: [PATCH 09/28] Revert some unneccessary measureContent crap I edited out --- src/components/PopoverWithMeasuredContent.js | 6 +++++- src/pages/home/report/ReportActionItem.js | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 7ef851912507..293be0b8f4c0 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -25,6 +25,10 @@ const propTypes = { horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + + // A function with content to measure. This component will use this.props.children by default, + // but in the case the children are not displayed, the measurement will not work. + measureContent: PropTypes.func.isRequired, }; const defaultProps = { @@ -122,7 +126,7 @@ class PopoverWithMeasuredContent extends Component { {...this.props} anchorPosition={this.calculateAdjustedAnchorPosition()} > - {this.props.children} + {this.props.measureContent()} ) : ( diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 49af83ef2e11..32c4102c0109 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -132,10 +132,17 @@ class ReportActionItem extends Component { /> ( + + )} > Date: Tue, 13 Apr 2021 23:50:35 -0700 Subject: [PATCH 10/28] Fix a couple bugs --- src/pages/home/report/ReportActionItem.js | 2 +- src/pages/home/report/ReportActionItemMessageEdit.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 32c4102c0109..19d2255df376 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -139,7 +139,7 @@ class ReportActionItem extends Component { measureContent={() => ( )} diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index a229dea6d68e..6e1677db5569 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -79,16 +79,16 @@ class ReportActionItemMessageEdit extends React.Component { style={[styles.textInput, styles.flex0]} /> - + Cancel - - + + Save Changes - + ); From 40c7c30157d11543de770a582e64d0ddde7bd75b Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 14 Apr 2021 22:47:36 -0700 Subject: [PATCH 11/28] Add edited indicator --- .../home/report/ReportActionItemFragment.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index a94cfebdb49c..931de9053cda 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes'; import styles from '../../../styles/styles'; +import variables from '../../../styles/variables'; import themeColors from '../../../styles/themes/default'; import RenderHTML from '../../../components/RenderHTML'; import Text from '../../../components/Text'; @@ -48,10 +49,17 @@ class ReportActionItemFragment extends React.PureComponent { } // Only render HTML if we have html in the fragment - return fragment.html !== fragment.text ? ( - - ) : ( - {Str.htmlDecode(fragment.text)} + return ( + + {fragment.html !== fragment.text ? ( + + ) : ( + {Str.htmlDecode(fragment.text)} + )} + {fragment.isEdited + ? (edited) + : null} + ); case 'TEXT': return ( From ca5d13e12509f527053b44591960bbf5df7b5ebc Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Fri, 23 Apr 2021 15:24:33 -0700 Subject: [PATCH 12/28] Add some comments, get this in more a polished state, PR comments --- src/components/PopoverWithMeasuredContent.js | 4 +-- src/libs/actions/Report.js | 20 +++++++++---- .../home/report/ReportActionContextMenu.js | 21 +++++++++---- src/pages/home/report/ReportActionItem.js | 4 ++- .../home/report/ReportActionItemFragment.js | 11 +++++-- .../report/ReportActionItemMessageEdit.js | 30 ++++++++++++++----- .../home/report/ReportActionItemSingle.js | 9 +++++- 7 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 293be0b8f4c0..03c0ec0553e7 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -126,7 +126,7 @@ class PopoverWithMeasuredContent extends Component { {...this.props} anchorPosition={this.calculateAdjustedAnchorPosition()} > - {this.props.measureContent()} + {this.props.children} ) : ( @@ -137,7 +137,7 @@ class PopoverWithMeasuredContent extends Component { but we can't measure its dimensions without first rendering it. */ - {this.props.children} + {this.props.measureContent()} ); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index ef5f39d65bc6..f535cbfa7987 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -896,10 +896,15 @@ NetworkConnection.onReconnect(() => { fetchAll(false); }); -function editReportComment(reportID, reportAction, htmlForNewComment) { +/** + * @param {Number} reportID + * @param {Object} originalReportAction + * @param {String} htmlForNewComment + */ +function editReportComment(reportID, originalReportAction, htmlForNewComment) { // Optimistically update the report action with the new message - const sequenceNumber = reportAction.sequenceNumber; - const newReportAction = {...reportAction}; + const sequenceNumber = originalReportAction.sequenceNumber; + const newReportAction = {...originalReportAction}; const actionToMerge = {}; newReportAction.message[0].isEdited = true; newReportAction.message[0].html = htmlForNewComment; @@ -910,9 +915,14 @@ function editReportComment(reportID, reportAction, htmlForNewComment) { // Persist the updated report comment API.Report_EditComment({ reportID, - reportActionID: reportAction.reportActionID, + reportActionID: originalReportAction.reportActionID, reportComment: htmlForNewComment, - }); + }) + .catch(() => { + // If it fails, reset Onyx + actionToMerge[sequenceNumber] = originalReportAction; + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge); + }); } function saveReportActionDraft(reportID, reportActionID, draftMessage) { diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index 103ef13428ce..cf5557ca21ff 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -30,15 +30,15 @@ const propTypes = { // Controls the visibility of this component. isVisible: PropTypes.bool, + // Draft message - if this is set the comment is in 'edit' mode + draftMessage: PropTypes.string, + /* Onyx Props */ // The session of the logged in person session: PropTypes.shape({ // Email of the logged in person email: PropTypes.string, }), - - // Draft message - if this is set the comment is in 'edit' mode - draftMessage: PropTypes.string, }; const defaultProps = { @@ -46,6 +46,7 @@ const defaultProps = { isMini: false, isVisible: false, session: {}, + draftMessage: '', }; class ReportActionContextMenu extends React.Component { @@ -119,6 +120,17 @@ class ReportActionContextMenu extends React.Component { }, ]; + constructor(props) { + super(props); + + this.getActionText = this.getActionText.bind(this); + } + + /** + * Gets the text (not HTML) portion of the message in an action. + * + * @return {*} + */ getActionText() { const message = _.last(lodashGet(this.props.reportAction, 'message', null)); return lodashGet(message, 'text', ''); @@ -151,7 +163,4 @@ export default withOnyx({ session: { key: ONYXKEYS.SESSION, }, - draftMessage: { - key: ({reportID, reportAction}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${reportAction.reportActionID}`, - }, })(ReportActionContextMenu); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 19d2255df376..98b9a94f8140 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -62,7 +62,7 @@ class ReportActionItem extends Component { || this.props.displayAsGroup !== nextProps.displayAsGroup || !_.isEqual(this.props.action, nextProps.action) || this.props.draftMessage !== nextProps.draftMessage - || (this.props.shouldDisplayNewIndicator !== nextProps.shouldDisplayNewIndicator); + || this.props.shouldDisplayNewIndicator !== nextProps.shouldDisplayNewIndicator; } /** @@ -128,6 +128,7 @@ class ReportActionItem extends Component { hovered && !this.state.isPopoverVisible } + draftMessage={this.props.draftMessage} isMini /> @@ -148,6 +149,7 @@ class ReportActionItem extends Component { isVisible={this.state.isPopoverVisible} reportID={this.props.reportID} reportAction={this.props.action} + draftMessage={this.props.draftMessage} /> diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 931de9053cda..ec40105150a5 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -57,8 +57,15 @@ class ReportActionItemFragment extends React.PureComponent { {Str.htmlDecode(fragment.text)} )} {fragment.isEdited - ? (edited) - : null} + ? ( + + (edited) + + ) : null} ); case 'TEXT': diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 6e1677db5569..564631ef2dce 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -6,7 +6,6 @@ import ReportActionPropTypes from './ReportActionPropTypes'; import styles from '../../../styles/styles'; import TextInputFocusable from '../../../components/TextInputFocusable'; import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; -import {TouchableOpacity} from 'react-native-web'; const propTypes = { // All the data of the action @@ -15,6 +14,7 @@ const propTypes = { // Draft message draftMessage: PropTypes.string.isRequired, + // ReportID that holds the comment we're editing reportID: PropTypes.number.isRequired, }; @@ -23,7 +23,7 @@ class ReportActionItemMessageEdit extends React.Component { super(props); this.updateDraft = this.updateDraft.bind(this); this.deleteDraft = this.deleteDraft.bind(this); - this.debouncedSaveDraft = _.debounce(this.debouncedSaveDraft.bind(this), 1000, false); + this.debouncedSaveDraft = _.debounce(this.debouncedSaveDraft.bind(this), 1000, true); this.publishDraft = this.publishDraft.bind(this); this.triggerSaveOrCancel = this.triggerSaveOrCancel.bind(this); @@ -43,19 +43,35 @@ class ReportActionItemMessageEdit extends React.Component { this.debouncedSaveDraft(trimmedNewDraft); } + /** + * Delete the draft of the comment being edited. This will take the comment out of "edit mode" with the old content. + */ deleteDraft() { saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, ''); } + /** + * Save the draft of the comment. This debounced so that we're not ceaselessly saving your edit. Saving the draft + * allows one to navigate somewhere else and come back to the comment and still have it in edit mode. + */ debouncedSaveDraft() { saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, this.state.draft); } + /** + * Save the draft of the comment to be the new comment message. This will take the comment out of "edit mode" with + * the new content. + */ publishDraft() { editReportComment(this.props.reportID, this.props.action, this.state.draft); this.deleteDraft(); } + /** + * Key event handlers that short cut to saving/canceling. + * + * @param {Event} e + */ triggerSaveOrCancel(e) { if (e && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); @@ -68,7 +84,7 @@ class ReportActionItemMessageEdit extends React.Component { render() { return ( - + this.textInput = el} @@ -79,13 +95,13 @@ class ReportActionItemMessageEdit extends React.Component { style={[styles.textInput, styles.flex0]} /> - - + + Cancel - - + + Save Changes diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 0bc936886dff..01e2d6de9c44 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -21,12 +21,19 @@ const propTypes = { // All of the personalDetails personalDetails: PropTypes.objectOf(personalDetailsPropType).isRequired, + // Draft message - if this is non-empty we'll render the comment in "edit mode" draftMessage: PropTypes.string.isRequired, + // ReportID containing the report action we're displaying reportID: PropTypes.number.isRequired, }; -const ReportActionItemSingle = ({action, personalDetails, draftMessage, reportID}) => { +const ReportActionItemSingle = ({ + action, + personalDetails, + draftMessage, + reportID, +}) => { const {avatar, displayName} = personalDetails[action.actorEmail] || {}; const avatarUrl = action.automatic ? `${CONST.CLOUDFRONT_URL}/images/icons/concierge_2019.svg` From 5d0536f29e52fec54a06fa2792e08ab921284804 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 27 Apr 2021 21:33:53 -0700 Subject: [PATCH 13/28] hidePopover --- src/libs/actions/Report.js | 9 +++++++++ src/pages/home/report/ReportActionContextMenu.js | 4 ++++ src/pages/home/report/ReportActionItem.js | 3 +++ 3 files changed, 16 insertions(+) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 0e21ea291b9f..c64c1bcbb357 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -901,6 +901,8 @@ NetworkConnection.onReconnect(() => { }); /** + * Saves a new message for a comment. Marks the comment as edited, which will be reflected in the UI. + * * @param {Number} reportID * @param {Object} originalReportAction * @param {String} htmlForNewComment @@ -929,6 +931,13 @@ function editReportComment(reportID, originalReportAction, htmlForNewComment) { }); } +/** + * Saves the draft for a comment report action. This will put the comment into "edit mode" + * + * @param {Number} reportID + * @param {Number} reportActionID + * @param {String} draftMessage + */ function saveReportActionDraft(reportID, reportActionID, draftMessage) { Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${reportActionID}`, draftMessage); } diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index cf5557ca21ff..35a43746ca55 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -33,6 +33,9 @@ const propTypes = { // Draft message - if this is set the comment is in 'edit' mode draftMessage: PropTypes.string, + // Function to dismiss the popover containing this menu + hidePopover: PropTypes.func.isRequired, + /* Onyx Props */ // The session of the logged in person session: PropTypes.shape({ @@ -103,6 +106,7 @@ class ReportActionContextMenu extends React.Component { shouldShow: this.props.reportAction.actorEmail === this.props.session.email && !isReportMessageAttachment(this.getActionText()), onPress: () => { + this.props.hidePopover(); saveReportActionDraft( this.props.reportID, this.props.reportAction.reportActionID, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 66e6fa1e58a5..0aa95f70ccee 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -129,6 +129,7 @@ class ReportActionItem extends Component { && !this.state.isPopoverVisible } draftMessage={this.props.draftMessage} + hidePopover={this.hidePopover} isMini /> @@ -143,6 +144,7 @@ class ReportActionItem extends Component { isVisible reportID={this.props.reportID} reportAction={this.props.action} + hidePopover={this.hidePopover} /> )} > @@ -151,6 +153,7 @@ class ReportActionItem extends Component { reportID={this.props.reportID} reportAction={this.props.action} draftMessage={this.props.draftMessage} + hidePopover={this.hidePopover} /> From ce406cd6e041f61bf120da710cf3896d7aec5a75 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 28 Apr 2021 10:13:16 -0700 Subject: [PATCH 14/28] Playing around with ReportScrollManager --- src/components/InvertedFlatList/index.js | 11 +++++++---- src/libs/ReportScrollManager.js | 12 ++++++++++++ src/pages/home/report/ReportActionItemMessageEdit.js | 5 +++++ src/pages/home/report/ReportActionsView.js | 7 +++---- 4 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 src/libs/ReportScrollManager.js diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index 6f9a861cb8a1..7d6fd6a4dd23 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -6,10 +6,13 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; import BaseInvertedFlatList from './BaseInvertedFlatList'; +import {FlatList} from 'react-native'; const propTypes = { // Passed via forwardRef so we can access the FlatList ref - innerRef: PropTypes.func.isRequired, + innerRef: PropTypes.shape({ + current: PropTypes.instanceOf(FlatList), + }).isRequired, }; // This is copied from https://codesandbox.io/s/react-native-dsyse @@ -22,9 +25,9 @@ const InvertedFlatList = (props) => { e.preventDefault(); }, []); - useEffect(() => { - props.innerRef(ref.current); - }, []); + // useEffect(() => { + // props.innerRef(ref.current); + // }, []); useEffect(() => { const currentRef = ref.current; diff --git a/src/libs/ReportScrollManager.js b/src/libs/ReportScrollManager.js new file mode 100644 index 000000000000..b9c14bfbecbe --- /dev/null +++ b/src/libs/ReportScrollManager.js @@ -0,0 +1,12 @@ +import React from 'react'; + +export const flatListRef = React.createRef(); + +function scrollToIndex(index) { + console.log(flatListRef); + return flatListRef.scrollToIndex(index); +} + +export { + scrollToIndex, +}; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 564631ef2dce..dd9944d1c674 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -6,6 +6,7 @@ import ReportActionPropTypes from './ReportActionPropTypes'; import styles from '../../../styles/styles'; import TextInputFocusable from '../../../components/TextInputFocusable'; import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; +import {scrollToIndex} from '../../../libs/ReportScrollManager'; const propTypes = { // All the data of the action @@ -93,6 +94,10 @@ class ReportActionItemMessageEdit extends React.Component { defaultValue={this.props.draftMessage} maxLines={16} // This is the same that slack has style={[styles.textInput, styles.flex0]} + onFocus={() => { + debugger; + scrollToIndex({animated: false, index: 0}); + }} /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 2661a20c400d..7d549f0e8436 100644 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -29,6 +29,7 @@ import themeColors from '../../../styles/themes/default'; import compose from '../../../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import withDrawerState, {withDrawerPropTypes} from '../../../components/withDrawerState'; +import {flatListRef, scrollToIndex} from '../../../libs/ReportScrollManager'; const propTypes = { // The ID of the report actions will be created for @@ -301,9 +302,7 @@ class ReportActionsView extends React.Component { * scroll the list to the end. As a report can contain non-message actions, we should confirm that list data exists. */ scrollToListBottom() { - if (this.actionListElement) { - this.actionListElement.scrollToIndex({animated: false, index: 0}); - } + scrollToIndex({animated: false, index: 0}); this.recordMaxAction(); } @@ -373,7 +372,7 @@ class ReportActionsView extends React.Component { return ( this.actionListElement = el} + ref={flatListRef} data={this.sortedReportActions} renderItem={this.renderItem} CellRendererComponent={this.renderCell} From a29f272367a2e69f31a593250779b057a1801a8b Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Fri, 30 Apr 2021 12:05:36 -0700 Subject: [PATCH 15/28] Scroll touch screens to comment when editing a comment --- src/components/InvertedFlatList/index.js | 11 ++++++++--- src/libs/ReportScrollManager/index.js | 15 +++++++++++++++ .../index.native.js} | 3 +-- src/pages/home/report/ReportActionItem.js | 5 ++++- .../home/report/ReportActionItemMessageEdit.js | 7 +++++-- src/pages/home/report/ReportActionsView.js | 1 + 6 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 src/libs/ReportScrollManager/index.js rename src/libs/{ReportScrollManager.js => ReportScrollManager/index.native.js} (65%) diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index 7d6fd6a4dd23..ad0dec6832f6 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -7,6 +7,7 @@ import React, { import PropTypes from 'prop-types'; import BaseInvertedFlatList from './BaseInvertedFlatList'; import {FlatList} from 'react-native'; +import _ from 'underscore'; const propTypes = { // Passed via forwardRef so we can access the FlatList ref @@ -25,9 +26,13 @@ const InvertedFlatList = (props) => { e.preventDefault(); }, []); - // useEffect(() => { - // props.innerRef(ref.current); - // }, []); + useEffect(() => { + if (!_.isFunction(props.innerRef)) { + props.innerRef.current = ref.current; + } else { + props.innerRef(ref.current); + } + }, []); useEffect(() => { const currentRef = ref.current; diff --git a/src/libs/ReportScrollManager/index.js b/src/libs/ReportScrollManager/index.js new file mode 100644 index 000000000000..7aa670eaf19a --- /dev/null +++ b/src/libs/ReportScrollManager/index.js @@ -0,0 +1,15 @@ +import React from 'react'; + +export const flatListRef = React.createRef(); + +function scrollToIndex(index, isEditing) { + if (isEditing) { + return; + } + + flatListRef.current.scrollToIndex(index); +} + +export { + scrollToIndex, +}; diff --git a/src/libs/ReportScrollManager.js b/src/libs/ReportScrollManager/index.native.js similarity index 65% rename from src/libs/ReportScrollManager.js rename to src/libs/ReportScrollManager/index.native.js index b9c14bfbecbe..4ad558bd3996 100644 --- a/src/libs/ReportScrollManager.js +++ b/src/libs/ReportScrollManager/index.native.js @@ -3,8 +3,7 @@ import React from 'react'; export const flatListRef = React.createRef(); function scrollToIndex(index) { - console.log(flatListRef); - return flatListRef.scrollToIndex(index); + return flatListRef.current.scrollToIndex(index); } export { diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 7d74ae8fb75d..a301a6f73099 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -42,6 +42,9 @@ const propTypes = { // Should we display the new indicator on top of the comment? shouldDisplayNewIndicator: PropTypes.bool.isRequired, + // Position index of the report action in the overall report FlatList view + index: PropTypes.number.isRequired, + /* --- Onyx Props --- */ // Draft message - if this is set the comment is in 'edit' mode draftMessage: PropTypes.string, @@ -124,7 +127,7 @@ class ReportActionItem extends Component { ) : _.isEmpty(this.props.draftMessage) ? - : ; + : ; return ( diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index dd9944d1c674..0270368d82e8 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -17,6 +17,9 @@ const propTypes = { // ReportID that holds the comment we're editing reportID: PropTypes.number.isRequired, + + // Position index of the report action in the overall report FlatList view + index: PropTypes.number.isRequired, }; class ReportActionItemMessageEdit extends React.Component { @@ -95,9 +98,9 @@ class ReportActionItemMessageEdit extends React.Component { maxLines={16} // This is the same that slack has style={[styles.textInput, styles.flex0]} onFocus={() => { - debugger; - scrollToIndex({animated: false, index: 0}); + scrollToIndex({animated: true, index: this.props.index}, true); }} + autoFocus /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 7d549f0e8436..ee6bcdd3f801 100644 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -351,6 +351,7 @@ class ReportActionsView extends React.Component { isMostRecentIOUReportAction={item.action.sequenceNumber === this.mostRecentIOUReportSequenceNumber} iouReportID={this.props.report.iouReportID} hasOutstandingIOU={this.props.report.hasOutstandingIOU} + index={index} /> ); } From da33bc699715d46550231306b12d107f45c05c8f Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Fri, 30 Apr 2021 15:31:07 -0700 Subject: [PATCH 16/28] Show/hide compose box when focusing on editing comment in mobile --- src/Expensify.js | 2 +- .../toggleReportActionComposeView/index.js | 1 + .../index.native.js | 4 +++ .../report/ReportActionItemMessageEdit.js | 4 +++ src/pages/home/report/ReportView.js | 32 +++++++++++++------ 5 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 src/libs/toggleReportActionComposeView/index.js create mode 100644 src/libs/toggleReportActionComposeView/index.native.js diff --git a/src/Expensify.js b/src/Expensify.js index 6d8fe67927b0..80085d2867c6 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -23,7 +23,7 @@ Onyx.init({ initialKeyStates: { // Clear any loading and error messages so they do not appear on app startup - [ONYXKEYS.SESSION]: {loading: false}, + [ONYXKEYS.SESSION]: {loading: false, shouldShowComposeInput: true}, [ONYXKEYS.ACCOUNT]: CONST.DEFAULT_ACCOUNT_DATA, [ONYXKEYS.NETWORK]: {isOffline: false}, [ONYXKEYS.IOU]: {loading: false}, diff --git a/src/libs/toggleReportActionComposeView/index.js b/src/libs/toggleReportActionComposeView/index.js new file mode 100644 index 000000000000..2d1ec238274a --- /dev/null +++ b/src/libs/toggleReportActionComposeView/index.js @@ -0,0 +1 @@ +export default () => {}; diff --git a/src/libs/toggleReportActionComposeView/index.native.js b/src/libs/toggleReportActionComposeView/index.native.js new file mode 100644 index 000000000000..1f0be323e489 --- /dev/null +++ b/src/libs/toggleReportActionComposeView/index.native.js @@ -0,0 +1,4 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../../ONYXKEYS'; + +export default shouldShowComposeInput => Onyx.merge(ONYXKEYS.SESSION, {shouldShowComposeInput}); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 0270368d82e8..1630e50b459d 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -7,6 +7,7 @@ import styles from '../../../styles/styles'; import TextInputFocusable from '../../../components/TextInputFocusable'; import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; import {scrollToIndex} from '../../../libs/ReportScrollManager'; +import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView'; const propTypes = { // All the data of the action @@ -52,6 +53,7 @@ class ReportActionItemMessageEdit extends React.Component { */ deleteDraft() { saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, ''); + toggleReportActionComposeView(true); } /** @@ -99,7 +101,9 @@ class ReportActionItemMessageEdit extends React.Component { style={[styles.textInput, styles.flex0]} onFocus={() => { scrollToIndex({animated: true, index: this.props.index}, true); + toggleReportActionComposeView(false); }} + onBlur={() => toggleReportActionComposeView(true)} autoFocus /> diff --git a/src/pages/home/report/ReportView.js b/src/pages/home/report/ReportView.js index b00a8fb50628..582332d45b59 100644 --- a/src/pages/home/report/ReportView.js +++ b/src/pages/home/report/ReportView.js @@ -7,25 +7,39 @@ import {addAction} from '../../../libs/actions/Report'; import KeyboardSpacer from '../../../components/KeyboardSpacer'; import styles from '../../../styles/styles'; import SwipeableView from '../../../components/SwipeableView'; +import {withOnyx} from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { /* The ID of the report the selected report */ reportID: PropTypes.number.isRequired, + + /* Onyx Keys */ + // Whether or not to show the Compose Input + session: PropTypes.shape({ + shouldShowComposeInput: PropTypes.bool.isRequired, + }).isRequired, }; -const ReportView = ({reportID}) => ( +const ReportView = ({reportID, session}) => ( - + - Keyboard.dismiss()}> - addAction(reportID, text)} - reportID={reportID} - /> - + {session.shouldShowComposeInput ? ( + Keyboard.dismiss()}> + addAction(reportID, text)} + reportID={reportID} + /> + + ) : null} ); ReportView.propTypes = propTypes; -export default ReportView; +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, +})(ReportView); From 1ef1bce02fa20928957a3aa607835946b59ea16b Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Fri, 30 Apr 2021 15:36:46 -0700 Subject: [PATCH 17/28] Linting --- src/components/InvertedFlatList/index.js | 3 ++- src/pages/home/report/ReportActionItem.js | 20 +++++++++++++++----- src/pages/home/report/ReportView.js | 4 ++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index ad0dec6832f6..2bf5a3844f9a 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -5,9 +5,9 @@ import React, { forwardRef, } from 'react'; import PropTypes from 'prop-types'; -import BaseInvertedFlatList from './BaseInvertedFlatList'; import {FlatList} from 'react-native'; import _ from 'underscore'; +import BaseInvertedFlatList from './BaseInvertedFlatList'; const propTypes = { // Passed via forwardRef so we can access the FlatList ref @@ -28,6 +28,7 @@ const InvertedFlatList = (props) => { useEffect(() => { if (!_.isFunction(props.innerRef)) { + // eslint-disable-next-line no-param-reassign props.innerRef.current = ref.current; } else { props.innerRef(ref.current); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index a301a6f73099..4631f6578488 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -116,18 +116,28 @@ class ReportActionItem extends Component { } render() { - const children = this.props.action.actionName === 'IOU' - ? ( + let children; + if (this.props.action.actionName === 'IOU') { + children = ( - ) - : _.isEmpty(this.props.draftMessage) + ); + } else { + children = _.isEmpty(this.props.draftMessage) ? - : ; + : ( + + ); + } return ( diff --git a/src/pages/home/report/ReportView.js b/src/pages/home/report/ReportView.js index 582332d45b59..c9f2caffa00c 100644 --- a/src/pages/home/report/ReportView.js +++ b/src/pages/home/report/ReportView.js @@ -1,13 +1,13 @@ import React from 'react'; import {Keyboard, View} from 'react-native'; import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; import ReportActionsView from './ReportActionsView'; import ReportActionCompose from './ReportActionCompose'; import {addAction} from '../../../libs/actions/Report'; import KeyboardSpacer from '../../../components/KeyboardSpacer'; import styles from '../../../styles/styles'; import SwipeableView from '../../../components/SwipeableView'; -import {withOnyx} from 'react-native-onyx'; import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { @@ -23,7 +23,7 @@ const propTypes = { const ReportView = ({reportID, session}) => ( - + {session.shouldShowComposeInput ? ( Keyboard.dismiss()}> From ffe69f10823c0577cfbbfd3a1c1dc2916894abc3 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 3 May 2021 22:11:50 -0700 Subject: [PATCH 18/28] PR Comments --- package-lock.json | 4 ++-- package.json | 2 +- src/libs/ReportScrollManager/index.js | 12 +++++++++++- src/libs/ReportScrollManager/index.native.js | 5 ++++- src/libs/actions/Report.js | 3 ++- .../home/report/ReportActionContextMenu.js | 2 +- src/pages/home/report/ReportActionItem.js | 6 +++--- .../home/report/ReportActionItemFragment.js | 19 +++++++++---------- .../report/ReportActionItemMessageEdit.js | 1 - src/pages/home/report/ReportView.js | 4 ++-- 10 files changed, 35 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index bd1cc0494f3b..75f2a8855cf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11562,8 +11562,8 @@ } }, "expensify-common": { - "version": "git+https://github.com/Expensify/expensify-common.git#d5edd0a956ef5c12fb6e9493d1f4608289f82a0e", - "from": "git+https://github.com/Expensify/expensify-common.git#d5edd0a956ef5c12fb6e9493d1f4608289f82a0e", + "version": "git+https://github.com/Expensify/expensify-common.git#758b46bc0b50320a006c7892faec56c2d3952135", + "from": "git+https://github.com/Expensify/expensify-common.git#758b46bc0b50320a006c7892faec56c2d3952135", "requires": { "classnames": "2.2.5", "clipboard": "2.0.4", diff --git a/package.json b/package.json index d1719d5d85db..7329b8a4b396 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "electron-log": "^4.2.4", "electron-serve": "^1.0.0", "electron-updater": "^4.3.4", - "expensify-common": "git+https://github.com/Expensify/expensify-common.git#d5edd0a956ef5c12fb6e9493d1f4608289f82a0e", + "expensify-common": "git+https://github.com/Expensify/expensify-common.git#758b46bc0b50320a006c7892faec56c2d3952135", "file-loader": "^6.0.0", "html-entities": "^1.3.1", "lodash": "4.17.21", diff --git a/src/libs/ReportScrollManager/index.js b/src/libs/ReportScrollManager/index.js index 7aa670eaf19a..ddd34b1bf6e6 100644 --- a/src/libs/ReportScrollManager/index.js +++ b/src/libs/ReportScrollManager/index.js @@ -1,7 +1,16 @@ import React from 'react'; -export const flatListRef = React.createRef(); +// This ref is created using React.createRef here because this function is used by a component that doesn't have access +// to the original ref. +const flatListRef = React.createRef(); +/** + * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because + * we are editing a comment. + * + * @param {Object} index + * @param {Boolean} isEditing + */ function scrollToIndex(index, isEditing) { if (isEditing) { return; @@ -11,5 +20,6 @@ function scrollToIndex(index, isEditing) { } export { + flatListRef, scrollToIndex, }; diff --git a/src/libs/ReportScrollManager/index.native.js b/src/libs/ReportScrollManager/index.native.js index 4ad558bd3996..0e18c4fdaf9a 100644 --- a/src/libs/ReportScrollManager/index.native.js +++ b/src/libs/ReportScrollManager/index.native.js @@ -1,11 +1,14 @@ import React from 'react'; -export const flatListRef = React.createRef(); +// This ref is created using React.createRef here because this function is used by a component that doesn't have access +// to the original ref. +const flatListRef = React.createRef(); function scrollToIndex(index) { return flatListRef.current.scrollToIndex(index); } export { + flatListRef, scrollToIndex, }; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 42559d61bdf4..5e0a2e20e4f2 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2,6 +2,7 @@ import moment from 'moment'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import * as Pusher from '../Pusher/pusher'; @@ -1014,7 +1015,7 @@ function editReportComment(reportID, originalReportAction, htmlForNewComment) { const actionToMerge = {}; newReportAction.message[0].isEdited = true; newReportAction.message[0].html = htmlForNewComment; - newReportAction.message[0].text = htmlForNewComment.replace(/<[^>]*>?/gm, ''); + newReportAction.message[0].text = Str.stripHTML(htmlForNewComment); actionToMerge[sequenceNumber] = newReportAction; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge); diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index 35a43746ca55..088db5db3f8a 100644 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -133,7 +133,7 @@ class ReportActionContextMenu extends React.Component { /** * Gets the text (not HTML) portion of the message in an action. * - * @return {*} + * @return {String} */ getActionText() { const message = _.last(lodashGet(this.props.reportAction, 'message', null)); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 4631f6578488..2298ed24d42d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -81,8 +81,8 @@ class ReportActionItem extends Component { || this.props.isMostRecentIOUReportAction !== nextProps.isMostRecentIOUReportAction || this.props.hasOutstandingIOU !== nextProps.hasOutstandingIOU || this.props.iouReportID !== nextProps.iouReportID - || !_.isEqual(this.props.action, nextProps.action - || this.props.shouldDisplayNewIndicator !== nextProps.shouldDisplayNewIndicator); + || this.props.shouldDisplayNewIndicator !== nextProps.shouldDisplayNewIndicator + || !_.isEqual(this.props.action, nextProps.action); } /** @@ -127,7 +127,7 @@ class ReportActionItem extends Component { /> ); } else { - children = _.isEmpty(this.props.draftMessage) + children = !this.props.draftMessage ? : ( {Str.htmlDecode(fragment.text)} )} - {fragment.isEdited - ? ( - - (edited) - - ) : null} + {fragment.isEdited && ( + + (edited) + + )} ); case 'TEXT': diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 1630e50b459d..8239dab61b90 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -93,7 +93,6 @@ class ReportActionItemMessageEdit extends React.Component { this.textInput = el} onChangeText={this.updateDraft} // Debounced saveDraftComment onKeyPress={this.triggerSaveOrCancel} defaultValue={this.props.draftMessage} diff --git a/src/pages/home/report/ReportView.js b/src/pages/home/report/ReportView.js index c9f2caffa00c..a1d724641d51 100644 --- a/src/pages/home/report/ReportView.js +++ b/src/pages/home/report/ReportView.js @@ -25,14 +25,14 @@ const ReportView = ({reportID, session}) => ( - {session.shouldShowComposeInput ? ( + {session.shouldShowComposeInput && ( Keyboard.dismiss()}> addAction(reportID, text)} reportID={reportID} /> - ) : null} + )} ); From 1aca583d0892abb49b8da157d8aeab169f59054b Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 4 May 2021 08:10:34 -0700 Subject: [PATCH 19/28] Remove uneccessary blur event --- src/pages/home/report/ReportActionItemMessageEdit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 8239dab61b90..c8248235b5a2 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -102,7 +102,6 @@ class ReportActionItemMessageEdit extends React.Component { scrollToIndex({animated: true, index: this.props.index}, true); toggleReportActionComposeView(false); }} - onBlur={() => toggleReportActionComposeView(true)} autoFocus /> From 4fd791d5f55571835ff9924a56752e5b57f751de Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 5 May 2021 21:34:38 -0700 Subject: [PATCH 20/28] Update when recieving an edited message via pusher event --- src/libs/Pusher/EventType.js | 1 + src/libs/actions/Report.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/libs/Pusher/EventType.js b/src/libs/Pusher/EventType.js index 866c96110388..0810261af524 100644 --- a/src/libs/Pusher/EventType.js +++ b/src/libs/Pusher/EventType.js @@ -4,5 +4,6 @@ */ export default { REPORT_COMMENT: 'reportComment', + REPORT_COMMENT_EDIT: 'reportCommentEdit', REPORT_TOGGLE_PINNED: 'reportTogglePinned', }; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 325dfbe32621..63cf29756cfc 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -425,6 +425,19 @@ function removeOptimisticActions(reportID) { }); } +/** + * Updates a report action's message to be a new value. + * + * @param {Number} reportID + * @param {Number} sequenceNumber + * @param {Object} message + */ +function updateReportActionMessage(reportID, sequenceNumber, message) { + const actionToMerge = {}; + actionToMerge[sequenceNumber] = {message: [message]}; + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge); +} + /** * Updates a report in the store with a new report action * @@ -579,6 +592,24 @@ function subscribeToUserEvents() { ); }); + // Live-update a report's actions when an 'edit comment' event is received. + Pusher.subscribe(pusherChannelName, Pusher.TYPE.REPORT_COMMENT_EDIT, (pushJSON) => { + Log.info( + `[Report] Handled ${Pusher.TYPE.REPORT_COMMENT_EDIT} event sent by Pusher`, true, {reportActionID: pushJSON.reportActionID}, + ); + updateReportActionMessage(pushJSON.reportID, pushJSON.sequenceNumber, pushJSON.message); + }, false, + () => { + NetworkConnection.triggerReconnectionCallbacks('pusher re-subscribed to private user channel'); + }) + .catch((error) => { + Log.info( + '[Report] Failed to subscribe to Pusher channel', + true, + {error, pusherChannelName, eventName: Pusher.TYPE.REPORT_COMMENT_EDIT}, + ); + }); + // Live-update a report's pinned state when a 'report toggle pinned' event is received. Pusher.subscribe(pusherChannelName, Pusher.TYPE.REPORT_TOGGLE_PINNED, (pushJSON) => { Log.info( @@ -1064,6 +1095,7 @@ function editReportComment(reportID, originalReportAction, htmlForNewComment) { reportID, reportActionID: originalReportAction.reportActionID, reportComment: htmlForNewComment, + sequenceNumber, }) .catch(() => { // If it fails, reset Onyx From 243819ea6ffd8ad75d75f6f26767fadfad263aef Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 5 May 2021 22:07:58 -0700 Subject: [PATCH 21/28] Hide report compose when editing comment on all small screens --- src/libs/toggleReportActionComposeView.js | 8 ++++++++ src/libs/toggleReportActionComposeView/index.js | 1 - src/libs/toggleReportActionComposeView/index.native.js | 4 ---- src/pages/home/report/ReportActionItemMessageEdit.js | 8 ++++++-- 4 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 src/libs/toggleReportActionComposeView.js delete mode 100644 src/libs/toggleReportActionComposeView/index.js delete mode 100644 src/libs/toggleReportActionComposeView/index.native.js diff --git a/src/libs/toggleReportActionComposeView.js b/src/libs/toggleReportActionComposeView.js new file mode 100644 index 000000000000..3b43305ebafb --- /dev/null +++ b/src/libs/toggleReportActionComposeView.js @@ -0,0 +1,8 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../ONYXKEYS'; + +export default (shouldShowComposeInput, isSmallScreenWidth) => { + if (isSmallScreenWidth) { + Onyx.merge(ONYXKEYS.SESSION, {shouldShowComposeInput}); + } +}; diff --git a/src/libs/toggleReportActionComposeView/index.js b/src/libs/toggleReportActionComposeView/index.js deleted file mode 100644 index 2d1ec238274a..000000000000 --- a/src/libs/toggleReportActionComposeView/index.js +++ /dev/null @@ -1 +0,0 @@ -export default () => {}; diff --git a/src/libs/toggleReportActionComposeView/index.native.js b/src/libs/toggleReportActionComposeView/index.native.js deleted file mode 100644 index 1f0be323e489..000000000000 --- a/src/libs/toggleReportActionComposeView/index.native.js +++ /dev/null @@ -1,4 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '../../ONYXKEYS'; - -export default shouldShowComposeInput => Onyx.merge(ONYXKEYS.SESSION, {shouldShowComposeInput}); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index c8248235b5a2..3ad2abd25ce9 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -8,6 +8,7 @@ import TextInputFocusable from '../../../components/TextInputFocusable'; import {editReportComment, saveReportActionDraft} from '../../../libs/actions/Report'; import {scrollToIndex} from '../../../libs/ReportScrollManager'; import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; const propTypes = { // All the data of the action @@ -21,6 +22,9 @@ const propTypes = { // Position index of the report action in the overall report FlatList view index: PropTypes.number.isRequired, + + /* Window Dimensions Props */ + ...windowDimensionsPropTypes, }; class ReportActionItemMessageEdit extends React.Component { @@ -53,7 +57,7 @@ class ReportActionItemMessageEdit extends React.Component { */ deleteDraft() { saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, ''); - toggleReportActionComposeView(true); + toggleReportActionComposeView(true, this.props.isSmallScreenWidth); } /** @@ -122,4 +126,4 @@ class ReportActionItemMessageEdit extends React.Component { } ReportActionItemMessageEdit.propTypes = propTypes; -export default ReportActionItemMessageEdit; +export default withWindowDimensions(ReportActionItemMessageEdit); From 4809f1174e80bda63c56a1681476f003afec0eb3 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 5 May 2021 22:11:38 -0700 Subject: [PATCH 22/28] Line length --- src/libs/actions/Report.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 63cf29756cfc..909089d15104 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -595,7 +595,9 @@ function subscribeToUserEvents() { // Live-update a report's actions when an 'edit comment' event is received. Pusher.subscribe(pusherChannelName, Pusher.TYPE.REPORT_COMMENT_EDIT, (pushJSON) => { Log.info( - `[Report] Handled ${Pusher.TYPE.REPORT_COMMENT_EDIT} event sent by Pusher`, true, {reportActionID: pushJSON.reportActionID}, + `[Report] Handled ${Pusher.TYPE.REPORT_COMMENT_EDIT} event sent by Pusher`, true, { + reportActionID: pushJSON.reportActionID, + }, ); updateReportActionMessage(pushJSON.reportID, pushJSON.sequenceNumber, pushJSON.message); }, false, From a2259c2b96920967cbef4502e7e152d45a0e386c Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 10 May 2021 12:26:21 -0700 Subject: [PATCH 23/28] Add doc for scrollToIndex --- src/libs/ReportScrollManager/index.native.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportScrollManager/index.native.js b/src/libs/ReportScrollManager/index.native.js index 0e18c4fdaf9a..a52e78c5a9b5 100644 --- a/src/libs/ReportScrollManager/index.native.js +++ b/src/libs/ReportScrollManager/index.native.js @@ -4,8 +4,13 @@ import React from 'react'; // to the original ref. const flatListRef = React.createRef(); +/** + * Scroll to the provided index. + * + * @param {Object} index + */ function scrollToIndex(index) { - return flatListRef.current.scrollToIndex(index); + flatListRef.current.scrollToIndex(index); } export { From dfae939cd1f4452c27141ff2da59f0c56027c44e Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 10 May 2021 12:39:21 -0700 Subject: [PATCH 24/28] Dumb auto styling fix --- src/pages/home/report/ReportActionContextMenu.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index 2f7ca65803fd..602998417e75 100755 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -123,8 +123,7 @@ class ReportActionContextMenu extends React.Component { text: this.props.translate('reportActionContextMenu.deleteComment'), icon: Trashcan, shouldShow: false, - onPress: () => { - }, + onPress: () => {}, }, ]; From e7abc093cc7a40970154c9ebba7f594ff82794bf Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 10 May 2021 13:41:29 -0700 Subject: [PATCH 25/28] PR Comments --- src/components/TextInputFocusable/index.js | 3 ++- src/components/TextInputFocusable/index.native.js | 3 ++- src/pages/home/report/ReportView.js | 12 ++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/TextInputFocusable/index.js b/src/components/TextInputFocusable/index.js index a66d8ef8ff0f..a99e23c8ab78 100755 --- a/src/components/TextInputFocusable/index.js +++ b/src/components/TextInputFocusable/index.js @@ -18,7 +18,7 @@ const propTypes = { onPasteFile: PropTypes.func, // A ref to forward to the text input - forwardedRef: PropTypes.func.isRequired, + forwardedRef: PropTypes.func, // General styles to apply to the text input // eslint-disable-next-line react/forbid-prop-types @@ -62,6 +62,7 @@ const defaultProps = { onDrop: () => {}, isDisabled: false, autoFocus: false, + forwardedRef: null, }; const IMAGE_EXTENSIONS = { diff --git a/src/components/TextInputFocusable/index.native.js b/src/components/TextInputFocusable/index.native.js index f54704f2669b..fab67ddb7308 100644 --- a/src/components/TextInputFocusable/index.native.js +++ b/src/components/TextInputFocusable/index.native.js @@ -13,7 +13,7 @@ const propTypes = { shouldClear: PropTypes.bool, // A ref to forward to the text input - forwardedRef: PropTypes.func.isRequired, + forwardedRef: PropTypes.func, // When the input has cleared whoever owns this input should know about it onClear: PropTypes.func, @@ -31,6 +31,7 @@ const defaultProps = { onClear: () => {}, autoFocus: false, isDisabled: false, + forwardedRef: null, }; class TextInputFocusable extends React.Component { diff --git a/src/pages/home/report/ReportView.js b/src/pages/home/report/ReportView.js index a1d724641d51..f6d132a451ae 100644 --- a/src/pages/home/report/ReportView.js +++ b/src/pages/home/report/ReportView.js @@ -17,8 +17,14 @@ const propTypes = { /* Onyx Keys */ // Whether or not to show the Compose Input session: PropTypes.shape({ - shouldShowComposeInput: PropTypes.bool.isRequired, - }).isRequired, + shouldShowComposeInput: PropTypes.bool, + }), +}; + +const defaultProps = { + session: { + shouldShowComposeInput: true, + }, }; const ReportView = ({reportID, session}) => ( @@ -38,6 +44,8 @@ const ReportView = ({reportID, session}) => ( ); ReportView.propTypes = propTypes; +ReportView.defaultProps = defaultProps; + export default withOnyx({ session: { key: ONYXKEYS.SESSION, From f4156d01c71e2d3f704c18b825d60d9250847d25 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 10 May 2021 15:33:47 -0700 Subject: [PATCH 26/28] Make sure we have a reportActionID if we're going to allow you to edit a comment --- src/pages/home/report/ReportActionContextMenu.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js index 602998417e75..92cbbd7df18c 100755 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -108,7 +108,8 @@ class ReportActionContextMenu extends React.Component { text: this.props.translate('reportActionContextMenu.editComment'), icon: Pencil, shouldShow: this.props.reportAction.actorEmail === this.props.session.email - && !isReportMessageAttachment(this.getActionText()), + && !isReportMessageAttachment(this.getActionText()) + && this.props.reportAction.reportActionID, onPress: () => { this.props.hidePopover(); saveReportActionDraft( From 89f75376776f751330684c3692ebba2e57281f3e Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 10 May 2021 16:42:27 -0700 Subject: [PATCH 27/28] Resolve console error when textInput has no focus method --- src/pages/home/report/ReportActionCompose.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index b738c5436dba..d1e999a2e83a 100755 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -170,7 +170,7 @@ class ReportActionCompose extends React.Component { * Focus the composer text input */ focus() { - if (this.textInput) { + if (this.textInput && this.textInput.focus) { // There could be other animations running while we trigger manual focus. // This prevents focus from making those animations janky. InteractionManager.runAfterInteractions(() => { From 80118209135140462449ccc7eb553f25f962d17b Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Mon, 10 May 2021 17:14:46 -0700 Subject: [PATCH 28/28] Keyboard persists taps handled --- src/pages/home/report/ReportActionsView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index e99ec9f4a810..87d950da39a9 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -401,6 +401,7 @@ class ReportActionsView extends React.Component { ListFooterComponent={this.state.isLoadingMoreChats ? : null} + keyboardShouldPersistTaps="handled" /> ); }