Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mention suggestions #18469

Merged
merged 16 commits into from
May 10, 2023
Merged
5 changes: 5 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -1011,12 +1011,17 @@ const CONST = {
CODE_2FA: /^\d{6}$/,
ATTACHMENT_ID: /chat-attachments\/(\d+)/,
HAS_COLON_ONLY_AT_THE_BEGINNING: /^:[^:]+$/,
HAS_AT_MOST_TWO_AT_SIGNS: /^@[^@]*@?[^@]*$/,

// eslint-disable-next-line no-misleading-character-class
NEW_LINE_OR_WHITE_SPACE_OR_EMOJI: /[\n\s\p{Extended_Pictographic}\u200d\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}\u20E3\uFE0F]|[#*0-9]\uFE0F?\u20E3/gu,

// Define the regular expression pattern to match a string starting with a colon and ending with a space or newline character
EMOJI_REPLACER: /^:[^\n\r]+?(?=$|\s)/,

// Define the regular expression pattern to match a string starting with an at sign and ending with a space or newline character
MENTION_REPLACER: /^@[^\n\r]*?(?=$|\s)/,

MERGED_ACCOUNT_PREFIX: /^(MERGED_\d+@)/,
},

Expand Down
111 changes: 111 additions & 0 deletions src/components/MentionSuggestions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import styles from '../styles/styles';
import * as StyleUtils from '../styles/StyleUtils';
import Text from './Text';
import CONST from '../CONST';
import Avatar from './Avatar';
import AutoCompleteSuggestions from './AutoCompleteSuggestions';
import getStyledTextArray from '../libs/GetStyledTextArray';
import avatarPropTypes from './avatarPropTypes';

const propTypes = {
/** The index of the highlighted mention */
highlightedMentionIndex: PropTypes.number,

/** Array of suggested mentions */
mentions: PropTypes.arrayOf(
PropTypes.shape({
/** Display name of the user */
text: PropTypes.string,

/** Email/phone number of the user */
alternateText: PropTypes.string,

/** Array of icons of the user. We use the first element of this array */
icons: PropTypes.arrayOf(avatarPropTypes),
}),
).isRequired,

/** Fired when the user selects an mention */
onSelect: PropTypes.func.isRequired,

/** Mention prefix that follows the @ sign */
prefix: PropTypes.string.isRequired,

/** Show that we can use large mention picker.
* Depending on available space and whether the input is expanded, we can have a small or large mention suggester.
* When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */
isMentionPickerLarge: PropTypes.bool.isRequired,

/** Show that we should include ReportRecipientLocalTime view height */
shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired,
};

const defaultProps = {
highlightedMentionIndex: 0,
};

/**
* Create unique keys for each mention item
* @param {Object} item
* @param {Number} index
* @returns {String}
*/
const keyExtractor = (item) => item.alternateText;

const MentionSuggestions = (props) => {
/**
* Render a suggestion menu item component.
* @param {Object} item
* @returns {JSX.Element}
*/
const renderSuggestionMenuItem = (item) => {
const displayedText = _.uniq([item.text, item.alternateText]).join(' - ');
const styledTextArray = getStyledTextArray(displayedText, props.prefix);

return (
<View style={[styles.autoCompleteSuggestionContainer, styles.ph2]}>
<Avatar
source={item.icons[0].source}
size={CONST.AVATAR_SIZE.SMALLER}
name={item.icons[0].name}
type={item.icons[0].type}
/>
<Text
style={styles.mentionSuggestionsText}
numberOfLines={1}
>
{_.map(styledTextArray, ({text, isColored}, i) => (
<Text
key={`${text}${i}`}
style={StyleUtils.getColoredBackgroundStyle(isColored)}
>
{text}
</Text>
))}
</Text>
</View>
);
};

return (
<AutoCompleteSuggestions
suggestions={props.mentions}
renderSuggestionMenuItem={renderSuggestionMenuItem}
keyExtractor={keyExtractor}
highlightedSuggestionIndex={props.highlightedMentionIndex}
onSelect={props.onSelect}
isSuggestionPickerLarge={props.isMentionPickerLarge}
shouldIncludeReportRecipientLocalTimeHeight={props.shouldIncludeReportRecipientLocalTimeHeight}
/>
);
};

MentionSuggestions.propTypes = propTypes;
MentionSuggestions.defaultProps = defaultProps;
MentionSuggestions.displayName = 'MentionSuggestions';

export default MentionSuggestions;
2 changes: 1 addition & 1 deletion src/libs/GetStyledTextArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Str from 'expensify-common/lib/str';
const getStyledTextArray = (name, prefix) => {
const texts = [];
const prefixLowercase = prefix.toLowerCase();
const prefixLocation = name.search(Str.escapeForRegExp(prefixLowercase));
const prefixLocation = name.toLowerCase().search(Str.escapeForRegExp(prefixLowercase));

if (prefixLocation === 0 && prefix.length === name.length) {
texts.push({text: prefixLowercase, isColored: true});
Expand Down
Loading