diff --git a/src/CONST.js b/src/CONST.js
index b98cf6cf70ec..bb628a57b521 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -42,6 +42,12 @@ const CONST = {
LIMIT: 50,
TYPE: {
IOU: 'IOU',
+ ADDCOMMENT: 'ADDCOMMENT',
+ },
+ },
+ MESSAGE: {
+ TYPE: {
+ COMMENT: 'COMMENT',
},
},
TYPE: {
diff --git a/src/components/Hoverable/HoverablePropTypes.js b/src/components/Hoverable/HoverablePropTypes.js
index 2ed9094ffe3a..f6ebf25fa883 100644
--- a/src/components/Hoverable/HoverablePropTypes.js
+++ b/src/components/Hoverable/HoverablePropTypes.js
@@ -16,12 +16,16 @@ const propTypes = {
/** Function that executes when the mouse leaves the children. */
onHoverOut: PropTypes.func,
+
+ // If the mouse clicks outside, should we dismiss hover?
+ resetsOnClickOutside: PropTypes.bool,
};
const defaultProps = {
containerStyle: {},
onHoverIn: () => {},
onHoverOut: () => {},
+ resetsOnClickOutside: false,
};
export {
diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js
index 50823936d9ef..e11b63aea8c0 100644
--- a/src/components/Hoverable/index.js
+++ b/src/components/Hoverable/index.js
@@ -72,7 +72,7 @@ class Hoverable extends Component {
if (!this.state.isHovered) {
return;
}
- if (this.wrapperView && !this.wrapperView.contains(event.target)) {
+ if (this.wrapperView && !this.wrapperView.contains(event.target) && this.props.resetsOnClickOutside) {
this.setIsHovered(false);
}
}
diff --git a/src/languages/en.js b/src/languages/en.js
index 078e46d16310..3013f21ad119 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -26,6 +26,7 @@ export default {
email: 'Email',
and: 'and',
details: 'Details',
+ delete: 'Delete',
contacts: 'Contacts',
recents: 'Recents',
},
@@ -84,6 +85,7 @@ export default {
markAsUnread: 'Mark as Unread',
editComment: 'Edit Comment',
deleteComment: 'Delete Comment',
+ deleteConfirmation: 'Are you sure you want to delete this comment?',
},
reportActionsView: {
beFirstPersonToComment: 'Be the first person to comment',
diff --git a/src/libs/API.js b/src/libs/API.js
index b1ef25c70a9c..36ab90483c5a 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -535,9 +535,9 @@ function Report_TogglePinned(parameters) {
/**
* @param {Object} parameters
* @param {Number} parameters.reportID
- * @param {String} parameters.reportActionID
+ * @param {Number} parameters.reportActionID
* @param {String} parameters.reportComment
- * @return {Promise}
+ * @returns {Promise}
*/
function Report_EditComment(parameters) {
const commandName = 'Report_EditComment';
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index da4fafc484db..b9c1008958c4 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -1004,7 +1004,7 @@ function addAction(reportID, text, file) {
// Optimistically add the new comment to the store before waiting to save it to the server
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {
[optimisticReportActionID]: {
- actionName: 'ADDCOMMENT',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
actorEmail: currentUserEmail,
actorAccountID: currentUserAccountID,
person: [
@@ -1023,7 +1023,7 @@ function addAction(reportID, text, file) {
timestamp: moment().unix(),
message: [
{
- type: 'COMMENT',
+ type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
html: htmlForNewComment,
text: textForNewComment,
},
@@ -1049,6 +1049,48 @@ function addAction(reportID, text, file) {
.then(({reportAction}) => updateReportWithNewAction(reportID, reportAction));
}
+/**
+ * Deletes a comment from the report, basically sets it as empty string
+ *
+ * @param {Number} reportID
+ * @param {Object} reportAction
+ */
+function deleteReportComment(reportID, reportAction) {
+ // Optimistic Response
+ const reportActionsToMerge = {};
+ const oldMessage = {...reportAction.message};
+ reportActionsToMerge[reportAction.sequenceNumber] = {
+ ...reportAction,
+ message: [
+ {
+ type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
+ html: '',
+ text: '',
+ },
+ ],
+ };
+
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge);
+
+ // Try to delete the comment by calling the API
+ API.Report_EditComment({
+ reportID,
+ reportActionID: reportAction.reportActionID,
+ reportComment: '',
+ })
+ .then((response) => {
+ if (response.jsonCode !== 200) {
+ // Reverse Optimistic Response
+ reportActionsToMerge[reportAction.sequenceNumber] = {
+ ...reportAction,
+ message: oldMessage,
+ };
+
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge);
+ }
+ });
+}
+
/**
* Updates the last read action ID on the report. It optimistically makes the change to the store, and then let's the
* network layer handle the delayed write.
@@ -1219,5 +1261,6 @@ export {
updateCurrentlyViewedReportID,
editReportComment,
saveReportActionDraft,
+ deleteReportComment,
getSimplifiedIOUReport,
};
diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js
index 99c1486923c2..c3822e3cc896 100755
--- a/src/pages/home/report/ReportActionContextMenu.js
+++ b/src/pages/home/report/ReportActionContextMenu.js
@@ -8,7 +8,9 @@ import {
Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark,
} from '../../../components/Icon/Expensicons';
import getReportActionContextMenuStyles from '../../../styles/getReportActionContextMenuStyles';
-import {setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft} from '../../../libs/actions/Report';
+import {
+ setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, deleteReportComment,
+} from '../../../libs/actions/Report';
import ReportActionContextMenuItem from './ReportActionContextMenuItem';
import ReportActionPropTypes from './ReportActionPropTypes';
import Clipboard from '../../../libs/Clipboard';
@@ -16,6 +18,8 @@ import compose from '../../../libs/compose';
import {isReportMessageAttachment} from '../../../libs/reportUtils';
import ONYXKEYS from '../../../ONYXKEYS';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
+import ConfirmModal from '../../../components/ConfirmModal';
+import CONST from '../../../CONST';
const propTypes = {
/** The ID of the report this report action is attached to. */
@@ -63,8 +67,13 @@ class ReportActionContextMenu extends React.Component {
constructor(props) {
super(props);
+ this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this);
+ this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this);
+ this.getActionText = this.getActionText.bind(this);
+ this.canEdit = this.canEdit.bind(this);
+
// A list of all the context actions in this menu.
- this.CONTEXT_ACTIONS = [
+ this.contextActions = [
// Copy to clipboard
{
text: this.props.translate('reportActionContextMenu.copyToClipboard'),
@@ -112,9 +121,10 @@ class ReportActionContextMenu extends React.Component {
{
text: this.props.translate('reportActionContextMenu.editComment'),
icon: Pencil,
- shouldShow: this.props.reportAction.actorEmail === this.props.session.email
+ shouldShow: () => (
+ this.canEdit()
&& !isReportMessageAttachment(this.getActionText())
- && this.props.reportAction.reportActionID,
+ ),
onPress: () => {
this.props.hidePopover();
saveReportActionDraft(
@@ -124,18 +134,19 @@ class ReportActionContextMenu extends React.Component {
);
},
},
-
{
text: this.props.translate('reportActionContextMenu.deleteComment'),
icon: Trashcan,
- shouldShow: false,
- onPress: () => {},
+ shouldShow: this.canEdit,
+ onPress: () => this.setState({isDeleteCommentConfirmModalVisible: true}),
},
];
this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini);
- this.getActionText = this.getActionText.bind(this);
+ this.state = {
+ isDeleteCommentConfirmModalVisible: false,
+ };
}
/**
@@ -148,10 +159,33 @@ class ReportActionContextMenu extends React.Component {
return lodashGet(message, 'text', '');
}
+ /**
+ * Can the current user edit this report action?
+ *
+ * @return {Boolean}
+ */
+ canEdit() {
+ // Can only edit if it's a ADDCOMMENT, the author is this user and it's not a optimistic response.
+ // If it's an optimistic response comment it will not have a reportActionID,
+ // and we should wait until it does before we show the actions
+ return this.props.reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT
+ && this.props.reportAction.actorEmail === this.props.session.email
+ && this.props.reportAction.reportActionID;
+ }
+
+ confirmDeleteAndHideModal() {
+ deleteReportComment(this.props.reportID, this.props.reportAction);
+ this.setState({isDeleteCommentConfirmModalVisible: false});
+ }
+
+ hideDeleteConfirmModal() {
+ this.setState({isDeleteCommentConfirmModalVisible: false});
+ }
+
render() {
return this.props.isVisible && (
- {this.CONTEXT_ACTIONS.map(contextAction => contextAction.shouldShow && (
+ {this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && (
contextAction.onPress(this.props.reportAction)}
/>
))}
+
);
}
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index adf69996b0e8..58b20f50743f 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -19,6 +19,7 @@ import ReportActionItemIOUAction from '../../../components/ReportActionItemIOUAc
import ReportActionItemMessage from './ReportActionItemMessage';
import UnreadActionIndicator from '../../../components/UnreadActionIndicator';
import ReportActionItemMessageEdit from './ReportActionItemMessageEdit';
+import CONST from '../../../CONST';
const propTypes = {
/** The ID of the report this action is on. */
@@ -197,7 +198,7 @@ class ReportActionItem extends Component {
render() {
let children;
- if (this.props.action.actionName === 'IOU') {
+ if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) {
children = (
this.popoverAnchor = el}
onSecondaryInteraction={this.showPopover}
>
-
+
{hovered => (
{this.props.shouldDisplayNewIndicator && (
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index fb4555fff4f0..39d215aed6e2 100755
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -248,7 +248,14 @@ class ReportActionsView extends React.Component {
updateSortedReportActions(reportActions) {
this.sortedReportActions = _.chain(reportActions)
.sortBy('sequenceNumber')
- .filter(action => action.actionName === 'ADDCOMMENT' || action.actionName === 'IOU')
+ .filter((action) => {
+ // Only show non-empty ADDCOMMENT actions or IOU actions
+ // Empty ADDCOMMENT actions typically mean they have been deleted and should not be shown
+ const message = _.first(lodashGet(action, 'message', null));
+ const html = lodashGet(message, 'html', '');
+ return action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU
+ || (action.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && html !== '');
+ })
.map((item, index) => ({action: item, index}))
.value()
.reverse();
@@ -291,7 +298,7 @@ class ReportActionsView extends React.Component {
updateMostRecentIOUReportActionNumber(reportActions) {
this.mostRecentIOUReportSequenceNumber = _.chain(reportActions)
.sortBy('sequenceNumber')
- .filter(action => action.actionName === 'IOU')
+ .filter(action => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU)
.max(action => action.sequenceNumber)
.value().sequenceNumber;
}
diff --git a/src/pages/iou/IOUTransactions.js b/src/pages/iou/IOUTransactions.js
index 0a5e9347ba2e..a505b21172d0 100644
--- a/src/pages/iou/IOUTransactions.js
+++ b/src/pages/iou/IOUTransactions.js
@@ -7,6 +7,7 @@ import styles from '../../styles/styles';
import ONYXKEYS from '../../ONYXKEYS';
import ReportActionPropTypes from '../home/report/ReportActionPropTypes';
import ReportTransaction from '../../components/ReportTransaction';
+import CONST from '../../CONST';
const propTypes = {
/** Actions from the ChatReport */
@@ -30,7 +31,7 @@ const IOUTransactions = ({
}) => (
{_.map(reportActions, (reportAction) => {
- if (reportAction.actionName === 'IOU'
+ if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU
&& reportAction.originalMessage.IOUReportID === iouReportID) {
return (
{};
PushNotification.deregister = () => {};
@@ -42,7 +43,7 @@ describe('actions/Report', () => {
const REPORT_ID = 1;
const ACTION_ID = 1;
const REPORT_ACTION = {
- actionName: 'ADDCOMMENT',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
actorAccountID: TEST_USER_ACCOUNT_ID,
actorEmail: TEST_USER_LOGIN,
automatic: false,