Skip to content
2 changes: 2 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ const ONYXKEYS = {
REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_',
REPORT_ACTIONS_REACTIONS: 'reportActionsReactions_',
REPORT_DRAFT_COMMENT: 'reportDraftComment_',
LAST_SELECTED_MENTION_SUGGESTION: 'lastSelectedMentionSuggestion_',
REPORT_DRAFT_COMMENT_NUMBER_OF_LINES: 'reportDraftCommentNumberOfLines_',
REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_',
REPORT_USER_IS_TYPING: 'reportUserIsTyping_',
Expand Down Expand Up @@ -481,6 +482,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: OnyxTypes.ReportActionsDrafts;
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions;
[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string;
[ONYXKEYS.COLLECTION.LAST_SELECTED_MENTION_SUGGESTION]: string;
[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES]: number;
[ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE]: boolean;
[ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: OnyxTypes.ReportUserIsTyping;
Expand Down
9 changes: 9 additions & 0 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,14 @@ function setReportWithDraft(reportID: string, hasDraft: boolean): Promise<void>
return Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {hasDraft});
}

/**
* Saves the last chosen mention from mention suggestion list for the report.
* This is used to determine if the mention suggestion list should be opened/remain opened.
*/
function saveReportDraftLastMention(reportID: string, comment: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_MENTION_SUGGESTION}${reportID}`, comment);
}

/** Broadcasts whether or not a user is typing on a report over the report's private pusher channel. */
function broadcastUserIsTyping(reportID: string) {
const privateReportChannelName = getReportChannelName(reportID);
Expand Down Expand Up @@ -3022,4 +3030,5 @@ export {
updateReportName,
resolveActionableMentionWhisper,
updateRoomVisibility,
saveReportDraftLastMention,
};
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ function ComposerWithSuggestions(
composerHeight={composerHeight}
measureParentContainer={measureParentContainer}
isAutoSuggestionPickerLarge={isAutoSuggestionPickerLarge}
reportID={reportID}
// Input
value={value}
setValue={setValue}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type SuggestionEmojiOnyxProps = {
preferredSkinTone: number;
};

type SuggestionEmojiProps = SuggestionProps &
type SuggestionEmojiProps = Omit<SuggestionProps, 'reportID'> &
SuggestionEmojiOnyxProps & {
/** Function to clear the input */
resetKeyboardInput?: () => void;
Expand Down
37 changes: 31 additions & 6 deletions src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Str from 'expensify-common/lib/str';
import lodashSortBy from 'lodash/sortBy';
import type {ForwardedRef} from 'react';
import type {ForwardedRef, RefAttributes} from 'react';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import * as Expensicons from '@components/Icon/Expensicons';
import type {Mention} from '@components/MentionSuggestions';
import MentionSuggestions from '@components/MentionSuggestions';
Expand All @@ -10,11 +12,13 @@ import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import * as Report from '@libs/actions/Report';
import * as LoginUtils from '@libs/LoginUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as SuggestionsUtils from '@libs/SuggestionUtils';
import * as UserUtils from '@libs/UserUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetailsList} from '@src/types/onyx';
import type {SuggestionsRef} from './ReportActionCompose';
import type {SuggestionProps} from './Suggestions';
Expand All @@ -38,8 +42,15 @@ const defaultSuggestionsValues: SuggestionValues = {
mentionPrefix: '',
};

type SuggestionMentionOnyxProps = {
/** The last selected mention suggestion */
lastSelectedMentionSuggestion: OnyxEntry<string>;
};

type SuggestionMentionProps = SuggestionProps & SuggestionMentionOnyxProps;

function SuggestionMention(
{value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused}: SuggestionProps,
{value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused, lastSelectedMentionSuggestion, reportID}: SuggestionMentionProps,
ref: ForwardedRef<SuggestionsRef>,
) {
const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT;
Expand Down Expand Up @@ -97,8 +108,9 @@ function SuggestionMention(
...prevState,
suggestedMentions: [],
}));
Report.saveReportDraftLastMention(reportID, mentionCode);
},
[value, suggestionValues.atSignIndex, suggestionValues.suggestedMentions, suggestionValues.mentionPrefix, updateComment, setSelection, formatLoginPrivateDomain],
[value, suggestionValues.atSignIndex, suggestionValues.suggestedMentions, suggestionValues.mentionPrefix.length, formatLoginPrivateDomain, updateComment, setSelection, reportID],
);

/**
Expand Down Expand Up @@ -222,6 +234,11 @@ function SuggestionMention(
let suggestionWord = '';
let prefix: string;

if (lastSelectedMentionSuggestion && (lastWord === lastSelectedMentionSuggestion || (!lastWord && secondToLastWord === lastSelectedMentionSuggestion))) {
resetSuggestions();
return;
}

// Detect if the last two words contain a mention (two words are needed to detect a mention with a space in it)
if (lastWord.startsWith('@')) {
atSignIndex = leftString.lastIndexOf(lastWord) + afterLastBreakLineIndex;
Expand Down Expand Up @@ -258,10 +275,14 @@ function SuggestionMention(
}));
setHighlightedMentionIndex(0);
},
[getMentionOptions, personalDetails, resetSuggestions, setHighlightedMentionIndex, value, isComposerFocused],
[isComposerFocused, value, lastSelectedMentionSuggestion, reportID, setHighlightedMentionIndex, resetSuggestions, getMentionOptions, personalDetails],
);

useEffect(() => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if ((!value && lastSelectedMentionSuggestion) || (lastSelectedMentionSuggestion && !value.includes(lastSelectedMentionSuggestion))) {
Report.saveReportDraftLastMention(reportID, '');
}
if (value.length < previousValue.length) {
// A workaround to not show the suggestions list when the user deletes a character before the mention.
// It is caused by a buggy behavior of the TextInput on iOS. Should be fixed after migration to Fabric.
Expand All @@ -270,7 +291,7 @@ function SuggestionMention(
}

calculateMentionSuggestion(selection.end);
}, [selection, value, previousValue, calculateMentionSuggestion]);
}, [selection, value, previousValue, calculateMentionSuggestion, lastSelectedMentionSuggestion, reportID]);

const updateShouldShowSuggestionMenuToFalse = useCallback(() => {
setSuggestionValues((prevState) => {
Expand Down Expand Up @@ -320,4 +341,8 @@ function SuggestionMention(

SuggestionMention.displayName = 'SuggestionMention';

export default forwardRef(SuggestionMention);
export default withOnyx<SuggestionMentionProps & RefAttributes<SuggestionsRef>, SuggestionMentionOnyxProps>({
lastSelectedMentionSuggestion: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.LAST_SELECTED_MENTION_SUGGESTION}${reportID}`,
},
})(forwardRef(SuggestionMention));
5 changes: 5 additions & 0 deletions src/pages/home/report/ReportActionCompose/Suggestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type SuggestionProps = {

/** The height of the composer */
composerHeight?: number;

/** The report ID */
reportID: string;
};

/**
Expand All @@ -66,6 +69,7 @@ function Suggestions(
measureParentContainer,
isAutoSuggestionPickerLarge = true,
isComposerFocused,
reportID,
}: SuggestionProps,
ref: ForwardedRef<SuggestionsRef>,
) {
Expand Down Expand Up @@ -167,6 +171,7 @@ function Suggestions(
/>
<SuggestionMention
ref={suggestionMentionRef}
reportID={reportID}
// eslint-disable-next-line react/jsx-props-no-spreading
{...baseProps}
/>
Expand Down