diff --git a/src/languages/en.ts b/src/languages/en.ts index a276de4e0f7c..817f06f6b344 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1547,12 +1547,6 @@ export default { invitePeople: 'Invite new members', genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', pleaseEnterValidLogin: `Please ensure the email or phone number is valid (e.g. ${CONST.EXAMPLE_PHONE_NUMBER}).`, - user: 'user', - users: 'users', - invited: 'invited', - removed: 'removed', - to: 'to', - from: 'from', }, inviteMessage: { inviteMessageTitle: 'Add message', diff --git a/src/languages/es.ts b/src/languages/es.ts index 290d80a6f65d..b219021daa0f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1569,12 +1569,6 @@ export default { invitePeople: 'Invitar nuevos miembros', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, - user: 'usuario', - users: 'usuarios', - invited: 'invitó', - removed: 'eliminó', - to: 'a', - from: 'de', }, inviteMessage: { inviteMessageTitle: 'Añadir un mensaje', diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 77c34ebdc576..488ff0d9b98a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,7 +1,6 @@ import * as RNLocalize from 'react-native-localize'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; -import {MessageElementBase, MessageTextElement} from '@libs/MessageElement'; import Config from '@src/CONFIG'; import CONST from '@src/CONST'; import translations from '@src/languages/translations'; @@ -122,48 +121,15 @@ function translateIfPhraseKey(message: MaybePhraseKey): string { } } -function getPreferredListFormat(): Intl.ListFormat { - if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) { - init(); - } - - return CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()]; -} - /** * Format an array into a string with comma and "and" ("a dog, a cat and a chicken") */ -function formatList(components: string[]) { - const listFormat = getPreferredListFormat(); - return listFormat.format(components); -} - -function formatMessageElementList(elements: readonly E[]): ReadonlyArray { - const listFormat = getPreferredListFormat(); - const parts = listFormat.formatToParts(elements.map((e) => e.content)); - const resultElements: Array = []; - - let nextElementIndex = 0; - for (const part of parts) { - if (part.type === 'element') { - /** - * The standard guarantees that all input elements will be present in the constructed parts, each exactly - * once, and without any modifications: https://tc39.es/ecma402/#sec-createpartsfromlist - */ - const element = elements[nextElementIndex++]; - - resultElements.push(element); - } else { - const literalElement: MessageTextElement = { - kind: 'text', - content: part.value, - }; - - resultElements.push(literalElement); - } +function arrayToString(anArray: string[]) { + if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) { + init(); } - - return resultElements; + const listFormat = CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()]; + return listFormat.format(anArray); } /** @@ -173,5 +139,5 @@ function getDevicePreferredLocale(): string { return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } -export {translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; +export {translate, translateLocal, translateIfPhraseKey, arrayToString, getDevicePreferredLocale}; export type {PhraseParameters, Phrase, MaybePhraseKey}; diff --git a/src/libs/MessageElement.ts b/src/libs/MessageElement.ts deleted file mode 100644 index 584d7e1e289a..000000000000 --- a/src/libs/MessageElement.ts +++ /dev/null @@ -1,11 +0,0 @@ -type MessageElementBase = { - readonly kind: string; - readonly content: string; -}; - -type MessageTextElement = { - readonly kind: 'text'; - readonly content: string; -} & MessageElementBase; - -export type {MessageElementBase, MessageTextElement}; diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 8a4151391453..560480dcec9d 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -197,18 +197,6 @@ function getFormattedAddress(privatePersonalDetails) { return formattedAddress.trim().replace(/,$/, ''); } -/** - * @param {Object} personalDetail - details object - * @returns {String | undefined} - The effective display name - */ -function getEffectiveDisplayName(personalDetail) { - if (personalDetail) { - return LocalePhoneNumber.formatPhoneNumber(personalDetail.login) || personalDetail.displayName; - } - - return undefined; -} - export { getDisplayNameOrDefault, getPersonalDetailsByIDs, @@ -218,5 +206,4 @@ export { getFormattedAddress, getFormattedStreet, getStreetLines, - getEffectiveDisplayName, }; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 2bc24c1706e6..6dc735ebd8b7 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -5,17 +5,14 @@ import OnyxUtils from 'react-native-onyx/lib/utils'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {ActionName, ChangeLog} from '@src/types/onyx/OriginalMessage'; +import {ActionName} from '@src/types/onyx/OriginalMessage'; import Report from '@src/types/onyx/Report'; -import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; +import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject'; import * as CollectionUtils from './CollectionUtils'; import * as Environment from './Environment/Environment'; import isReportMessageAttachment from './isReportMessageAttachment'; -import * as Localize from './Localize'; import Log from './Log'; -import {MessageElementBase, MessageTextElement} from './MessageElement'; -import * as PersonalDetailsUtils from './PersonalDetailsUtils'; type LastVisibleMessage = { lastMessageTranslationKey?: string; @@ -23,19 +20,6 @@ type LastVisibleMessage = { lastMessageHtml?: string; }; -type MemberChangeMessageUserMentionElement = { - readonly kind: 'userMention'; - readonly accountID: number; -} & MessageElementBase; - -type MemberChangeMessageRoomReferenceElement = { - readonly kind: 'roomReference'; - readonly roomName: string; - readonly roomID: number; -} & MessageElementBase; - -type MemberChangeMessageElement = MessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement; - const allReports: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, @@ -116,7 +100,7 @@ function isReimbursementQueuedAction(reportAction: OnyxEntry) { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED; } -function isMemberChangeAction(reportAction: OnyxEntry) { +function isChannelLogMemberAction(reportAction: OnyxEntry) { return ( reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM || @@ -125,10 +109,6 @@ function isMemberChangeAction(reportAction: OnyxEntry) { ); } -function isInviteMemberAction(reportAction: OnyxEntry) { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM; -} - function isReimbursementDeQueuedAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED; } @@ -669,89 +649,6 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return actions.includes(reportAction.actionName); } -function getMemberChangeMessageElements(reportAction: OnyxEntry): readonly MemberChangeMessageElement[] { - const isInviteAction = isInviteMemberAction(reportAction); - - // Currently, we only render messages when members are invited - const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); - - const originalMessage = reportAction?.originalMessage as ChangeLog; - const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; - const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); - - const mentionElements = targetAccountIDs.map((accountID): MemberChangeMessageUserMentionElement => { - const personalDetail = personalDetails.find((personal) => personal.accountID === accountID); - const handleText = PersonalDetailsUtils.getEffectiveDisplayName(personalDetail) ?? Localize.translateLocal('common.hidden'); - - return { - kind: 'userMention', - content: `@${handleText}`, - accountID, - }; - }); - - const buildRoomElements = (): readonly MemberChangeMessageElement[] => { - const roomName = originalMessage?.roomName; - - if (roomName) { - const preposition = isInviteAction ? ` ${Localize.translateLocal('workspace.invite.to')} ` : ` ${Localize.translateLocal('workspace.invite.from')} `; - - if (originalMessage.reportID) { - return [ - { - kind: 'text', - content: preposition, - }, - { - kind: 'roomReference', - roomName, - roomID: originalMessage.reportID, - content: roomName, - }, - ]; - } - } - - return []; - }; - - return [ - { - kind: 'text', - content: `${verb} `, - }, - ...Localize.formatMessageElementList(mentionElements), - ...buildRoomElements(), - ]; -} - -function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message { - const messageElements: readonly MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction); - const html = messageElements - .map((messageElement) => { - switch (messageElement.kind) { - case 'userMention': - return ``; - case 'roomReference': - return `${messageElement.roomName}`; - default: - return messageElement.content; - } - }) - .join(''); - - return { - html: `${html}`, - text: reportAction?.message ? reportAction?.message[0].text : '', - type: CONST.REPORT.MESSAGE.TYPE.COMMENT, - }; -} - -function getMemberChangeMessagePlainText(reportAction: OnyxEntry): string { - const messageElements = getMemberChangeMessageElements(reportAction); - return messageElements.map((element) => element.content).join(''); -} - /** * Helper method to determine if the provided accountID has made a request on the specified report. * @@ -814,9 +711,7 @@ export { shouldReportActionBeVisibleAsLastAction, hasRequestFromCurrentAccount, getFirstVisibleReportActionID, - isMemberChangeAction, - getMemberChangeMessageFragment, - getMemberChangeMessagePlainText, + isChannelLogMemberAction, isReimbursementDeQueuedAction, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 84b2283681c6..1266f145de30 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -18,7 +18,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx'; import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import {IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage'; +import {ChangeLog, IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage'; import {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; import DeepValueOf from '@src/types/utils/DeepValueOf'; @@ -4174,6 +4174,44 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry) }); } +/** + * Return room channel log display message + */ +function getChannelLogMemberMessage(reportAction: OnyxEntry): string { + const verb = + reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + ? 'invited' + : 'removed'; + + const mentions = (reportAction?.originalMessage as ChangeLog)?.targetAccountIDs?.map(() => { + const personalDetail = allPersonalDetails?.accountID; + const displayNameOrLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail?.login ?? '') || (personalDetail?.displayName ?? '') || Localize.translateLocal('common.hidden'); + return `@${displayNameOrLogin}`; + }); + + const lastMention = mentions?.pop(); + let message = ''; + + if (mentions?.length === 0) { + message = `${verb} ${lastMention}`; + } else if (mentions?.length === 1) { + message = `${verb} ${mentions?.[0]} and ${lastMention}`; + } else { + message = `${verb} ${mentions?.join(', ')}, and ${lastMention}`; + } + + const roomName = (reportAction?.originalMessage as ChangeLog)?.roomName ?? ''; + if (roomName) { + const preposition = + reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + ? ' to' + : ' from'; + message += `${preposition} ${roomName}`; + } + + return message; +} + /** * Checks if a report is a group chat. * @@ -4408,6 +4446,7 @@ export { getReimbursementQueuedActionMessage, getReimbursementDeQueuedActionMessage, getPersonalDetailsForAccountID, + getChannelLogMemberMessage, getRoom, shouldDisableWelcomeMessage, navigateToPrivateNotes, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 6e382e11b49b..bace29e06d28 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -375,17 +375,17 @@ function getOptionData( const targetAccountIDs = lastAction?.originalMessage?.targetAccountIDs ?? []; const verb = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? Localize.translate(preferredLocale, 'workspace.invite.invited') - : Localize.translate(preferredLocale, 'workspace.invite.removed'); - const users = Localize.translate(preferredLocale, targetAccountIDs.length > 1 ? 'workspace.invite.users' : 'workspace.invite.user'); + ? 'invited' + : 'removed'; + const users = targetAccountIDs.length > 1 ? 'users' : 'user'; result.alternateText = `${verb} ${targetAccountIDs.length} ${users}`; const roomName = lastAction?.originalMessage?.roomName ?? ''; if (roomName) { const preposition = lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ` ${Localize.translate(preferredLocale, 'workspace.invite.to')}` - : ` ${Localize.translate(preferredLocale, 'workspace.invite.from')}`; + ? ' to' + : ' from'; result.alternateText += `${preposition} ${roomName}`; } } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 6c645bc87486..4f35926c5957 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -281,8 +281,8 @@ export default [ } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); - } else if (ReportActionsUtils.isMemberChangeAction(reportAction)) { - const logMessage = ReportActionsUtils.getMemberChangeMessagePlainText(reportAction); + } else if (ReportActionsUtils.isChannelLogMemberAction(reportAction)) { + const logMessage = ReportUtils.getChannelLogMemberMessage(reportAction); Clipboard.setString(logMessage); } else if (content) { const parser = new ExpensiMark(); diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 46e0438f250a..2265530f29a1 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -8,7 +8,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import TextCommentFragment from './comment/TextCommentFragment'; import ReportActionItemFragment from './ReportActionItemFragment'; import reportActionPropTypes from './reportActionPropTypes'; @@ -41,20 +40,6 @@ function ReportActionItemMessage(props) { const styles = useThemeStyles(); const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); - if (ReportActionsUtils.isMemberChangeAction(props.action)) { - const fragment = ReportActionsUtils.getMemberChangeMessageFragment(props.action); - - return ( - - ); - } - let iouMessage; if (isIOUReport) { const iouReportID = lodashGet(props.action, 'originalMessage.IOUReportID'); diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 72ea275e3ba3..f76fbd5ffd7d 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -140,7 +140,6 @@ type ChronosOOOTimestamp = { type ChangeLog = { targetAccountIDs?: number[]; roomName?: string; - reportID?: number; }; type ChronosOOOEvent = { diff --git a/tests/unit/LocalizeTests.js b/tests/unit/LocalizeTests.js index 7693a0a4a88d..4c89d587fc06 100644 --- a/tests/unit/LocalizeTests.js +++ b/tests/unit/LocalizeTests.js @@ -15,7 +15,7 @@ describe('localize', () => { afterEach(() => Onyx.clear()); - describe('formatList', () => { + describe('arrayToString', () => { test.each([ [ [], @@ -52,9 +52,9 @@ describe('localize', () => { [CONST.LOCALES.ES]: 'rory, vit e ionatan', }, ], - ])('formatList(%s)', (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => { - expect(Localize.formatList(input)).toBe(expectedOutput); - return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(Localize.formatList(input)).toBe(expectedOutputES)); + ])('arrayToSpokenList(%s)', (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => { + expect(Localize.arrayToString(input)).toBe(expectedOutput); + return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(Localize.arrayToString(input)).toBe(expectedOutputES)); }); }); });