From 626f81f5953d9db07ba1e7fa147257031977c0dc Mon Sep 17 00:00:00 2001
From: Brayden Williams <1311325+redstar504@users.noreply.github.com>
Date: Tue, 7 Feb 2023 16:18:54 -0800
Subject: [PATCH] Account for markdown using debounced comment counter
---
src/CONST.js | 1 +
src/components/ExceededCommentLength.js | 61 +++++++++++++++----
src/libs/ReportUtils.js | 19 ++++--
src/pages/home/report/ReportActionCompose.js | 17 +++++-
.../report/ReportActionItemMessageEdit.js | 17 +++++-
5 files changed, 91 insertions(+), 24 deletions(-)
diff --git a/src/CONST.js b/src/CONST.js
index accd263483f4..30f9e24ae3e7 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -402,6 +402,7 @@ const CONST = {
SHOW_LOADING_SPINNER_DEBOUNCE_TIME: 250,
TOOLTIP_SENSE: 1000,
TRIE_INITIALIZATION: 'trie_initialization',
+ COMMENT_LENGTH_DEBOUNCE_TIME: 500,
},
PRIORITY_MODE: {
GSD: 'gsd',
diff --git a/src/components/ExceededCommentLength.js b/src/components/ExceededCommentLength.js
index 33f557c66d99..4ef6a5027e73 100644
--- a/src/components/ExceededCommentLength.js
+++ b/src/components/ExceededCommentLength.js
@@ -1,27 +1,62 @@
-import React from 'react';
+import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
+import {debounce} from 'lodash';
import CONST from '../CONST';
+import * as ReportUtils from '../libs/ReportUtils';
import Text from './Text';
import styles from '../styles/styles';
const propTypes = {
- /** The current length of the comment */
- commentLength: PropTypes.number.isRequired,
+ /** Text Comment */
+ comment: PropTypes.string.isRequired,
+
+ /** Update UI on parent when comment length is exceeded */
+ onExceededMaxCommentLength: PropTypes.func.isRequired,
};
-const ExceededCommentLength = (props) => {
- if (props.commentLength <= CONST.MAX_COMMENT_LENGTH) {
- return null;
+class ExceededCommentLength extends PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ commentLength: 0,
+ };
+
+ // By debouncing, we defer the calculation until there is a break in typing
+ this.updateCommentLength = debounce(this.updateCommentLength.bind(this), CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME);
}
- return (
-
- {`${props.commentLength}/${CONST.MAX_COMMENT_LENGTH}`}
-
- );
-};
+ componentDidMount() {
+ this.updateCommentLength();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.comment === this.props.comment) {
+ return;
+ }
+
+ this.updateCommentLength();
+ }
+
+ updateCommentLength() {
+ const commentLength = ReportUtils.getCommentLength(this.props.comment);
+ this.setState({commentLength});
+ this.props.onExceededMaxCommentLength(commentLength > CONST.MAX_COMMENT_LENGTH);
+ }
+
+ render() {
+ if (this.state.commentLength <= CONST.MAX_COMMENT_LENGTH) {
+ return null;
+ }
+
+ return (
+
+ {`${this.state.commentLength}/${CONST.MAX_COMMENT_LENGTH}`}
+
+ );
+ }
+}
ExceededCommentLength.propTypes = propTypes;
-ExceededCommentLength.displayName = 'ExceededCommentLength';
export default ExceededCommentLength;
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index c3ff12d2febf..8105482d3c02 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -774,6 +774,15 @@ function hasReportNameError(report) {
return !_.isEmpty(lodashGet(report, 'errorFields.reportName', {}));
}
+/**
+ * @param {String} text
+ * @returns {String}
+ */
+function getParsedComment(text) {
+ const parser = new ExpensiMark();
+ return text.length < CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : text;
+}
+
/**
* @param {String} [text]
* @param {File} [file]
@@ -783,7 +792,7 @@ function buildOptimisticAddCommentReportAction(text, file) {
// For comments shorter than 10k chars, convert the comment from MD into HTML because that's how it is stored in the database
// For longer comments, skip parsing and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!!
const parser = new ExpensiMark();
- const commentText = text.length < CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : text;
+ const commentText = getParsedComment(text);
const isAttachment = _.isEmpty(text) && file !== undefined;
const attachmentInfo = isAttachment ? file : {};
const htmlForNewComment = isAttachment ? 'Uploading Attachment...' : commentText;
@@ -1425,13 +1434,13 @@ function getNewMarkerReportActionID(report, sortedAndFilteredReportActions) {
}
/**
- * Replace code points > 127 with C escape sequences, and return the resulting string's overall length
- * Used for compatibility with the backend auth validator for AddComment
+ * Performs the markdown conversion, and replaces code points > 127 with C escape sequences
+ * Used for compatibility with the backend auth validator for AddComment, and to account for MD in comments
* @param {String} textComment
- * @returns {Number}
+ * @returns {Number} The comment's total length as seen from the backend
*/
function getCommentLength(textComment) {
- return textComment.replace(/[^ -~]/g, '\\u????').length;
+ return getParsedComment(textComment).replace(/[^ -~]/g, '\\u????').trim().length;
}
/**
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 0efb256e8144..dd6d208e1123 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -132,6 +132,7 @@ class ReportActionCompose extends React.Component {
this.getInputPlaceholder = this.getInputPlaceholder.bind(this);
this.getIOUOptions = this.getIOUOptions.bind(this);
this.addAttachment = this.addAttachment.bind(this);
+ this.setExceededMaxCommentLength = this.setExceededMaxCommentLength.bind(this);
this.comment = props.comment;
// React Native will retain focus on an input for native devices but web/mWeb behave differently so we have some focus management
@@ -153,6 +154,7 @@ class ReportActionCompose extends React.Component {
// If we are on a small width device then don't show last 3 items from conciergePlaceholderOptions
conciergePlaceholderRandomIndex: _.random(this.props.translate('reportActionCompose.conciergePlaceholderOptions').length - (this.props.isSmallScreenWidth ? 4 : 1)),
+ hasExceededMaxCommentLength: false,
};
}
@@ -302,6 +304,16 @@ class ReportActionCompose extends React.Component {
this.setState({maxLines});
}
+ /**
+ * Updates the composer when the comment length is exceeded
+ * Shows red borders and prevents the comment from being sent
+ *
+ * @param {Boolean} hasExceededMaxCommentLength
+ */
+ setExceededMaxCommentLength(hasExceededMaxCommentLength) {
+ this.setState({hasExceededMaxCommentLength});
+ }
+
isEmptyChat() {
return _.size(this.props.reportActions) === 1;
}
@@ -513,8 +525,7 @@ class ReportActionCompose extends React.Component {
const isComposeDisabled = this.props.isDrawerOpen && this.props.isSmallScreenWidth;
const isBlockedFromConcierge = ReportUtils.chatIncludesConcierge(this.props.report) && User.isBlockedFromConcierge(this.props.blockedFromConcierge);
const inputPlaceholder = this.getInputPlaceholder();
- const encodedCommentLength = ReportUtils.getCommentLength(this.comment);
- const hasExceededMaxCommentLength = encodedCommentLength > CONST.MAX_COMMENT_LENGTH;
+ const hasExceededMaxCommentLength = this.state.hasExceededMaxCommentLength;
return (
{!this.props.isSmallScreenWidth && }
-
+
{this.state.isDraggingOver && }
diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js
index abef7beb65ca..37369611f190 100644
--- a/src/pages/home/report/ReportActionItemMessageEdit.js
+++ b/src/pages/home/report/ReportActionItemMessageEdit.js
@@ -69,6 +69,7 @@ class ReportActionItemMessageEdit extends React.Component {
this.triggerSaveOrCancel = this.triggerSaveOrCancel.bind(this);
this.onSelectionChange = this.onSelectionChange.bind(this);
this.addEmojiToTextBox = this.addEmojiToTextBox.bind(this);
+ this.setExceededMaxCommentLength = this.setExceededMaxCommentLength.bind(this);
this.saveButtonID = 'saveButton';
this.cancelButtonID = 'cancelButton';
this.emojiButtonID = 'emojiButton';
@@ -84,6 +85,7 @@ class ReportActionItemMessageEdit extends React.Component {
end: draftMessage.length,
},
isFocused: false,
+ hasExceededMaxCommentLength: false,
};
}
@@ -96,6 +98,16 @@ class ReportActionItemMessageEdit extends React.Component {
this.setState({selection: e.nativeEvent.selection});
}
+ /**
+ * Updates the composer when the comment length is exceeded
+ * Shows red borders and prevents the comment from being sent
+ *
+ * @param {Boolean} hasExceededMaxCommentLength
+ */
+ setExceededMaxCommentLength(hasExceededMaxCommentLength) {
+ this.setState({hasExceededMaxCommentLength});
+ }
+
/**
* Update the value of the draft in Onyx
*
@@ -217,8 +229,7 @@ class ReportActionItemMessageEdit extends React.Component {
}
render() {
- const draftLength = ReportUtils.getCommentLength(this.state.draft);
- const hasExceededMaxCommentLength = draftLength > CONST.MAX_COMMENT_LENGTH;
+ const hasExceededMaxCommentLength = this.state.hasExceededMaxCommentLength;
return (
-
+
);