diff --git a/package-lock.json b/package-lock.json index 0d0e318c6d8f..955b87726868 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#9a68635cdcef4c81593c0f816a007bc9c707d46a", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -20282,8 +20282,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", - "integrity": "sha512-zz0/y0apISP1orxXEQOgn+Uod45O4wVypwwtaqcDPV4dH1tC3i4L98NoLSZvLn7Y17EcceSkfN6QCEsscgFTDQ==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#9a68635cdcef4c81593c0f816a007bc9c707d46a", + "integrity": "sha512-9BHjM3kZs7/dil0oykEQFkEhXjVD5liTttmO7ZYtPZkl4j6g97mubY2p9lYpWwpkWckUfvU7nGuZQjahw9xSFA==", "license": "MIT", "dependencies": { "classnames": "2.5.0", diff --git a/package.json b/package.json index b06805a5b9bb..72990eebdecc 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#9a68635cdcef4c81593c0f816a007bc9c707d46a", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index a26c368ae170..9373df336e78 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -164,7 +164,7 @@ function MoneyRequestView({ const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat - const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); + const isPolicyExpenseChat = ReportUtils.isReportInGroupPolicy(report); const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTagList), [policyTagList]); diff --git a/src/hooks/useHandleExceedMaxCommentLength.ts b/src/hooks/useHandleExceedMaxCommentLength.ts index fea0793c9854..69c1d7597164 100644 --- a/src/hooks/useHandleExceedMaxCommentLength.ts +++ b/src/hooks/useHandleExceedMaxCommentLength.ts @@ -1,14 +1,15 @@ import _ from 'lodash'; import {useCallback, useMemo, useState} from 'react'; import * as ReportUtils from '@libs/ReportUtils'; +import type {ParsingDetails} from '@libs/ReportUtils'; import CONST from '@src/CONST'; const useHandleExceedMaxCommentLength = () => { const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); const handleValueChange = useCallback( - (value: string) => { - if (ReportUtils.getCommentLength(value) <= CONST.MAX_COMMENT_LENGTH) { + (value: string, parsingDetails?: ParsingDetails) => { + if (ReportUtils.getCommentLength(value, parsingDetails) <= CONST.MAX_COMMENT_LENGTH) { if (hasExceededMaxCommentLength) { setHasExceededMaxCommentLength(false); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5c55f015cfeb..99bcf5ceb831 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -474,6 +474,11 @@ type OutstandingChildRequest = { hasOutstandingChildRequest?: boolean; }; +type ParsingDetails = { + shouldEscapeText?: boolean; + reportID?: string; +}; + let currentUserEmail: string | undefined; let currentUserPrivateDomain: string | undefined; let currentUserAccountID: number | undefined; @@ -881,12 +886,19 @@ function isControlPolicyExpenseChat(report: OnyxEntry): boolean { return isPolicyExpenseChat(report) && getPolicyType(report, allPolicies) === CONST.POLICY.TYPE.CORPORATE; } +/** + * Whether the provided policyType is a Free, Collect or Control policy type + */ +function isGroupPolicy(policyType: string): boolean { + return policyType === CONST.POLICY.TYPE.CORPORATE || policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.FREE; +} + /** * Whether the provided report belongs to a Free, Collect or Control policy */ -function isGroupPolicy(report: OnyxEntry): boolean { +function isReportInGroupPolicy(report: OnyxEntry): boolean { const policyType = getPolicyType(report, allPolicies); - return policyType === CONST.POLICY.TYPE.CORPORATE || policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.FREE; + return isGroupPolicy(policyType); } /** @@ -1454,7 +1466,7 @@ function canAddOrDeleteTransactions(moneyRequestReport: OnyxEntry): bool return false; } - if (isGroupPolicy(moneyRequestReport) && isProcessingReport(moneyRequestReport) && !PolicyUtils.isInstantSubmitEnabled(getPolicy(moneyRequestReport?.policyID))) { + if (isReportInGroupPolicy(moneyRequestReport) && isProcessingReport(moneyRequestReport) && !PolicyUtils.isInstantSubmitEnabled(getPolicy(moneyRequestReport?.policyID))) { return false; } @@ -3288,7 +3300,13 @@ function addDomainToShortMention(mention: string): string | undefined { * For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database * For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! */ -function getParsedComment(text: string, shouldEscapeText?: boolean): string { +function getParsedComment(text: string, parsingDetails?: ParsingDetails): string { + let isGroupPolicyReport = false; + if (parsingDetails?.reportID) { + const currentReport = getReport(parsingDetails?.reportID); + isGroupPolicyReport = isReportInGroupPolicy(currentReport); + } + const parser = new ExpensiMark(); const textWithMention = text.replace(CONST.REGEX.SHORT_MENTION, (match) => { const mention = match.substring(1); @@ -3296,7 +3314,9 @@ function getParsedComment(text: string, shouldEscapeText?: boolean): string { return mentionWithDomain ? `@${mentionWithDomain}` : match; }); - return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(textWithMention, {shouldEscapeText}) : lodashEscape(text); + return text.length <= CONST.MAX_MARKUP_LENGTH + ? parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) + : lodashEscape(text); } function getReportDescriptionText(report: Report): string { @@ -3355,9 +3375,16 @@ function buildOptimisticInviteReportAction(invitedUserDisplayName: string, invit }; } -function buildOptimisticAddCommentReportAction(text?: string, file?: FileObject, actorAccountID?: number, createdOffset = 0, shouldEscapeText?: boolean): OptimisticReportAction { +function buildOptimisticAddCommentReportAction( + text?: string, + file?: FileObject, + actorAccountID?: number, + createdOffset = 0, + shouldEscapeText?: boolean, + reportID?: string, +): OptimisticReportAction { const parser = new ExpensiMark(); - const commentText = getParsedComment(text ?? '', shouldEscapeText); + const commentText = getParsedComment(text ?? '', {shouldEscapeText, reportID}); const isAttachmentOnly = file && !text; const isTextOnly = text && !file; @@ -3479,7 +3506,7 @@ function buildOptimisticTaskCommentReportAction( childOldestFourAccountIDs?: string; }, ): OptimisticReportAction { - const reportAction = buildOptimisticAddCommentReportAction(text, undefined, undefined, createdOffset); + const reportAction = buildOptimisticAddCommentReportAction(text, undefined, undefined, createdOffset, undefined, taskReportID); if (reportAction.reportAction.message?.[0]) { reportAction.reportAction.message[0].taskReportID = taskReportID; } @@ -5209,8 +5236,8 @@ function getNewMarkerReportActionID(report: OnyxEntry, sortedAndFiltered * Used for compatibility with the backend auth validator for AddComment, and to account for MD in comments * @returns The comment's total length as seen from the backend */ -function getCommentLength(textComment: string): number { - return getParsedComment(textComment) +function getCommentLength(textComment: string, parsingDetails?: ParsingDetails): number { + return getParsedComment(textComment, parsingDetails) .replace(/[^ -~]/g, '\\u????') .trim().length; } @@ -5340,7 +5367,7 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o // which is tied to their workspace chat. if (isMoneyRequestReport(report)) { const canAddTransactions = canAddOrDeleteTransactions(report); - return isGroupPolicy(report) ? isOwnPolicyExpenseChat && canAddTransactions : canAddTransactions; + return isReportInGroupPolicy(report) ? isOwnPolicyExpenseChat && canAddTransactions : canAddTransactions; } // In the case of policy expense chat, users can only submit expenses from their own policy expense chat @@ -6241,7 +6268,7 @@ function canBeAutoReimbursed(report: OnyxEntry, policy: OnyxEntry= reimbursableTotal && reimbursableTotal > 0 && @@ -6601,7 +6628,7 @@ export { isExpensifyOnlyParticipantInReport, isGroupChat, isGroupChatAdmin, - isGroupPolicy, + isReportInGroupPolicy, isHoldCreator, isIOUOwnedByCurrentUser, isIOUReport, @@ -6688,4 +6715,5 @@ export type { OptionData, TransactionDetails, OptimisticInviteReportAction, + ParsingDetails, }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 34335c797212..c15fc4a967d5 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -450,7 +450,7 @@ function addActions(reportID: string, text = '', file?: FileObject) { let commandName: typeof WRITE_COMMANDS.ADD_COMMENT | typeof WRITE_COMMANDS.ADD_ATTACHMENT | typeof WRITE_COMMANDS.ADD_TEXT_AND_ATTACHMENT = WRITE_COMMANDS.ADD_COMMENT; if (text && !file) { - const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text); + const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text, undefined, undefined, undefined, undefined, reportID); reportCommentAction = reportComment.reportAction; reportCommentText = reportComment.commentText; } @@ -459,13 +459,13 @@ function addActions(reportID: string, text = '', file?: FileObject) { // When we are adding an attachment we will call AddAttachment. // It supports sending an attachment with an optional comment and AddComment supports adding a single text comment only. commandName = WRITE_COMMANDS.ADD_ATTACHMENT; - const attachment = ReportUtils.buildOptimisticAddCommentReportAction(text, file); + const attachment = ReportUtils.buildOptimisticAddCommentReportAction(text, file, undefined, undefined, undefined, reportID); attachmentAction = attachment.reportAction; } if (text && file) { // When there is both text and a file, the text for the report comment needs to be parsed) - reportCommentText = ReportUtils.getParsedComment(text ?? ''); + reportCommentText = ReportUtils.getParsedComment(text ?? '', {reportID}); // And the API command needs to go to the new API which supports combining both text and attachments in a single report action commandName = WRITE_COMMANDS.ADD_TEXT_AND_ATTACHMENT; @@ -1844,7 +1844,7 @@ function updateDescription(reportID: string, previousValue: string, newValue: st return; } - const parsedDescription = ReportUtils.getParsedComment(newValue); + const parsedDescription = ReportUtils.getParsedComment(newValue, {reportID}); const optimisticData: OnyxUpdate[] = [ { diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index b036666989dd..469a7300a84f 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -458,7 +458,7 @@ function ComposerWithSuggestions( const prepareCommentAndResetComposer = useCallback((): string => { const trimmedComment = commentRef.current.trim(); - const commentLength = ReportUtils.getCommentLength(trimmedComment); + const commentLength = ReportUtils.getCommentLength(trimmedComment, {reportID}); // Don't submit empty comments or comments that exceed the character limit if (!commentLength || commentLength > CONST.MAX_COMMENT_LENGTH) { diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 75d0c703b5b1..5bfa2475ee23 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -461,7 +461,7 @@ function ReportActionCompose({ if (value.length === 0 && isComposerFullSize) { Report.setIsComposerFullSize(reportID, false); } - validateCommentMaxLength(value); + validateCommentMaxLength(value, {reportID}); }} /> { // Do nothing if draft exceed the character limit - if (ReportUtils.getCommentLength(draft) > CONST.MAX_COMMENT_LENGTH) { + if (ReportUtils.getCommentLength(draft, {reportID}) > CONST.MAX_COMMENT_LENGTH) { return; } @@ -380,8 +380,8 @@ function ReportActionItemMessageEdit( const focus = focusComposerWithDelay(textInputRef.current); useEffect(() => { - validateCommentMaxLength(draft); - }, [draft, validateCommentMaxLength]); + validateCommentMaxLength(draft, {reportID}); + }, [draft, reportID, validateCommentMaxLength]); return ( <> diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index 93018fe34e5e..18b290a81ea4 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -49,7 +49,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) { // We first check if the report is part of a policy - if not, then it's a personal request (1:1 request) // For personal requests, we need to allow both users to put the request on hold - const isWorkspaceRequest = ReportUtils.isGroupPolicy(report); + const isWorkspaceRequest = ReportUtils.isReportInGroupPolicy(report); const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); const navigateBack = () => { diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index a571192f7a47..4b34a6a19600 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -73,7 +73,7 @@ function IOURequestStepCategory({ // The transactionCategory can be an empty string, so to maintain the logic we'd like to keep it in this shape until utils refactor // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowCategory = ReportUtils.isGroupPolicy(report) && (!!transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + const shouldShowCategory = ReportUtils.isReportInGroupPolicy(report) && (!!transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx index b50495ac47bd..bc6f71b23228 100644 --- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx +++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx @@ -63,7 +63,7 @@ function IOURequestStepMerchant({ const merchant = ReportUtils.getTransactionDetails(isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction)?.merchant; const isEmptyMerchant = merchant === '' || merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const isMerchantRequired = ReportUtils.isGroupPolicy(report) || transaction?.participants?.some((participant) => Boolean(participant.isPolicyExpenseChat)); + const isMerchantRequired = ReportUtils.isReportInGroupPolicy(report) || transaction?.participants?.some((participant) => Boolean(participant.isPolicyExpenseChat)); const navigateBack = () => { Navigation.goBack(backTo); }; diff --git a/src/pages/iou/request/step/IOURequestStepTag.tsx b/src/pages/iou/request/step/IOURequestStepTag.tsx index a62720cbd13a..ff1a1c01600d 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.tsx +++ b/src/pages/iou/request/step/IOURequestStepTag.tsx @@ -77,7 +77,7 @@ function IOURequestStepTag({ const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - const shouldShowTag = ReportUtils.isGroupPolicy(report) && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)); + const shouldShowTag = ReportUtils.isReportInGroupPolicy(report) && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = !shouldShowTag || (isEditing && (isSplitBill ? !canEditSplitBill : reportAction && !canEditMoneyRequest(reportAction)));