Page MenuHomePhabricator

D8897.id31500.diff
No OneTemporary

D8897.id31500.diff

diff --git a/lib/shared/mention-utils.js b/lib/shared/mention-utils.js
--- a/lib/shared/mention-utils.js
+++ b/lib/shared/mention-utils.js
@@ -21,6 +21,19 @@
+end: number,
};
+type TypeaheadUserSuggestionItem = {
+ +type: 'user',
+ +userInfo: RelativeMemberInfo,
+};
+
+export type TypeaheadSuggestionItem = TypeaheadUserSuggestionItem;
+
+export type TypeaheadTooltipActionItem<SuggestionItemType> = {
+ +key: string,
+ +execute: () => mixed,
+ +actionButtonContent: SuggestionItemType,
+};
+
// The simple-markdown package already breaks words out for us, and we are
// supposed to only match when the first word of the input matches
const markdownMentionRegex: RegExp = new RegExp(
@@ -67,7 +80,7 @@
threadMembers: $ReadOnlyArray<RelativeMemberInfo>,
viewerID: ?string,
usernamePrefix: string,
-): $ReadOnlyArray<RelativeMemberInfo> {
+): $ReadOnlyArray<TypeaheadUserSuggestionItem> {
const userIDs = userSearchIndex.getSearchResults(usernamePrefix);
const usersInThread = threadOtherMembers(threadMembers, viewerID);
@@ -75,26 +88,25 @@
.filter(user => usernamePrefix.length === 0 || userIDs.includes(user.id))
.sort((userA, userB) =>
stringForUserExplicit(userA).localeCompare(stringForUserExplicit(userB)),
- );
+ )
+ .map(userInfo => ({ type: 'user', userInfo }));
}
function getNewTextAndSelection(
textBeforeAtSymbol: string,
entireText: string,
- usernamePrefix: string,
- user: RelativeMemberInfo,
+ query: string,
+ suggestionText: string,
): {
newText: string,
newSelectionStart: number,
} {
- const totalMatchLength =
- textBeforeAtSymbol.length + usernamePrefix.length + 1; // 1 for @ char
+ const totalMatchLength = textBeforeAtSymbol.length + query.length + 1; // 1 for @ char
let newSuffixText = entireText.slice(totalMatchLength);
newSuffixText = (newSuffixText[0] !== ' ' ? ' ' : '') + newSuffixText;
- const newText =
- textBeforeAtSymbol + '@' + stringForUserExplicit(user) + newSuffixText;
+ const newText = textBeforeAtSymbol + suggestionText + newSuffixText;
const newSelectionStart = newText.length - newSuffixText.length + 1;
diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js
--- a/native/chat/chat-input-bar.react.js
+++ b/native/chat/chat-input-bar.react.js
@@ -90,6 +90,7 @@
} from './message-editing-context.react.js';
import type { RemoveEditMode } from './message-list-types.js';
import TypeaheadTooltip from './typeahead-tooltip.react.js';
+import MentionTypeaheadTooltipButton from '../chat/mention-typeahead-tooltip-button.react.js';
import Button from '../components/button.react.js';
// eslint-disable-next-line import/extensions
import ClearableTextInput from '../components/clearable-text-input.react';
@@ -129,7 +130,10 @@
import Alert from '../utils/alert.js';
import { runTiming } from '../utils/animation-utils.js';
import { exitEditAlert } from '../utils/edit-messages-utils.js';
-import { nativeTypeaheadRegex } from '../utils/typeahead-utils.js';
+import {
+ nativeTypeaheadRegex,
+ mentionTypeaheadTooltipActions,
+} from '../utils/typeahead-utils.js';
/* eslint-disable import/no-named-as-default-member */
const { Value, Clock, block, set, cond, neq, sub, interpolateNode, stopClock } =
@@ -567,8 +571,10 @@
<TypeaheadTooltip
text={this.state.text}
matchedStrings={typeaheadMatchedStrings}
- suggestedUsers={suggestedUsers}
+ suggestions={suggestedUsers}
focusAndUpdateTextAndSelection={this.focusAndUpdateTextAndSelection}
+ typeaheadTooltipActionsGetter={mentionTypeaheadTooltipActions}
+ TypeaheadTooltipButtonComponent={MentionTypeaheadTooltipButton}
/>
);
}
diff --git a/native/chat/mention-typeahead-tooltip-button.react.js b/native/chat/mention-typeahead-tooltip-button.react.js
new file mode 100644
--- /dev/null
+++ b/native/chat/mention-typeahead-tooltip-button.react.js
@@ -0,0 +1,61 @@
+// @flow
+
+import * as React from 'react';
+import { Text } from 'react-native';
+
+import type {
+ TypeaheadTooltipActionItem,
+ TypeaheadSuggestionItem,
+} from 'lib/shared/mention-utils.js';
+
+import UserAvatar from '../avatars/user-avatar.react.js';
+import Button from '../components/button.react.js';
+import { useStyles } from '../themes/colors.js';
+
+type Props = {
+ +item: TypeaheadTooltipActionItem<TypeaheadSuggestionItem>,
+};
+function MentionTypeaheadTooltipButton(props: Props): React.Node {
+ const { item } = props;
+ const styles = useStyles(unboundStyles);
+
+ let avatarComponent = null;
+ let typeaheadTooltipButtonText = null;
+ if (item.actionButtonContent.type === 'user') {
+ avatarComponent = (
+ <UserAvatar size="small" userID={item.actionButtonContent.userInfo.id} />
+ );
+ typeaheadTooltipButtonText = item.actionButtonContent.userInfo.username;
+ }
+
+ return (
+ <Button
+ onPress={item.execute}
+ style={styles.button}
+ iosActiveOpacity={0.85}
+ >
+ {avatarComponent}
+ <Text style={styles.buttonLabel} numberOfLines={1}>
+ @{typeaheadTooltipButtonText}
+ </Text>
+ </Button>
+ );
+}
+
+const unboundStyles = {
+ button: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ innerHeight: 24,
+ padding: 8,
+ color: 'typeaheadTooltipText',
+ },
+ buttonLabel: {
+ color: 'white',
+ fontSize: 16,
+ fontWeight: '400',
+ marginLeft: 8,
+ },
+};
+
+export default MentionTypeaheadTooltipButton;
diff --git a/native/chat/typeahead-tooltip.react.js b/native/chat/typeahead-tooltip.react.js
--- a/native/chat/typeahead-tooltip.react.js
+++ b/native/chat/typeahead-tooltip.react.js
@@ -1,67 +1,59 @@
// @flow
import * as React from 'react';
-import { Platform, Text } from 'react-native';
+import { Platform } from 'react-native';
import { PanGestureHandler, FlatList } from 'react-native-gesture-handler';
import {
type TypeaheadMatchedStrings,
type Selection,
- getNewTextAndSelection,
+ type TypeaheadTooltipActionItem,
} from 'lib/shared/mention-utils.js';
-import type { RelativeMemberInfo } from 'lib/types/thread-types.js';
-import UserAvatar from '../avatars/user-avatar.react.js';
-import Button from '../components/button.react.js';
import { useStyles } from '../themes/colors.js';
+import type {
+ TypeaheadTooltipActionsParams,
+ TypeaheadTooltipButtonComponentType,
+} from '../utils/typeahead-utils.js';
-export type TypeaheadTooltipProps = {
+export type TypeaheadTooltipProps<SuggestionItemType> = {
+text: string,
+matchedStrings: TypeaheadMatchedStrings,
- +suggestedUsers: $ReadOnlyArray<RelativeMemberInfo>,
+ +suggestions: $ReadOnlyArray<SuggestionItemType>,
+focusAndUpdateTextAndSelection: (text: string, selection: Selection) => void,
+ +typeaheadTooltipActionsGetter: (
+ TypeaheadTooltipActionsParams<SuggestionItemType>,
+ ) => $ReadOnlyArray<TypeaheadTooltipActionItem<SuggestionItemType>>,
+ +TypeaheadTooltipButtonComponent: TypeaheadTooltipButtonComponentType<SuggestionItemType>,
};
-function TypeaheadTooltip(props: TypeaheadTooltipProps): React.Node {
+function TypeaheadTooltip<SuggestionItemType>(
+ props: TypeaheadTooltipProps<SuggestionItemType>,
+): React.Node {
const {
text,
matchedStrings,
- suggestedUsers,
+ suggestions,
focusAndUpdateTextAndSelection,
+ TypeaheadTooltipButtonComponent,
+ typeaheadTooltipActionsGetter,
} = props;
const { textBeforeAtSymbol, query } = matchedStrings;
const styles = useStyles(unboundStyles);
-
- const renderTypeaheadButton = React.useCallback(
- ({ item }: { item: RelativeMemberInfo, ... }) => {
- const onPress = () => {
- const { newText, newSelectionStart } = getNewTextAndSelection(
- textBeforeAtSymbol,
- text,
- query,
- item,
- );
-
- focusAndUpdateTextAndSelection(newText, {
- start: newSelectionStart,
- end: newSelectionStart,
- });
- };
-
- return (
- <Button onPress={onPress} style={styles.button} iosActiveOpacity={0.85}>
- <UserAvatar size="small" userID={item.id} />
- <Text style={styles.buttonLabel} numberOfLines={1}>
- @{item.username}
- </Text>
- </Button>
- );
- },
+ const actions = React.useMemo(
+ () =>
+ typeaheadTooltipActionsGetter({
+ suggestions,
+ textBeforeAtSymbol,
+ text,
+ query,
+ focusAndUpdateTextAndSelection,
+ }),
[
- styles.button,
- styles.buttonLabel,
+ typeaheadTooltipActionsGetter,
+ suggestions,
textBeforeAtSymbol,
text,
query,
@@ -69,6 +61,16 @@
],
);
+ const renderTypeaheadButton = React.useCallback(
+ ({
+ item,
+ }: {
+ item: TypeaheadTooltipActionItem<SuggestionItemType>,
+ ...
+ }) => <TypeaheadTooltipButtonComponent item={item} />,
+ [],
+ );
+
// This is a hack that was introduced due to a buggy behavior of a
// absolutely positioned FlatList on Android.
@@ -95,17 +97,12 @@
<FlatList
style={styles.container}
contentContainerStyle={styles.contentContainer}
- data={suggestedUsers}
+ data={actions}
renderItem={renderTypeaheadButton}
keyboardShouldPersistTaps="always"
/>
),
- [
- renderTypeaheadButton,
- styles.container,
- styles.contentContainer,
- suggestedUsers,
- ],
+ [actions, renderTypeaheadButton, styles.container, styles.contentContainer],
);
const listWithConditionalHandler = React.useMemo(() => {
@@ -134,19 +131,6 @@
contentContainer: {
padding: 8,
},
- button: {
- alignItems: 'center',
- flexDirection: 'row',
- innerHeight: 24,
- padding: 8,
- color: 'typeaheadTooltipText',
- },
- buttonLabel: {
- color: 'white',
- fontSize: 16,
- fontWeight: '400',
- marginLeft: 8,
- },
};
export default TypeaheadTooltip;
diff --git a/native/utils/typeahead-utils.js b/native/utils/typeahead-utils.js
--- a/native/utils/typeahead-utils.js
+++ b/native/utils/typeahead-utils.js
@@ -1,6 +1,15 @@
// @flow
+import * as React from 'react';
+
import { oldValidUsernameRegexString } from 'lib/shared/account-utils.js';
+import {
+ getNewTextAndSelection,
+ type Selection,
+ type TypeaheadTooltipActionItem,
+ type TypeaheadSuggestionItem,
+} from 'lib/shared/mention-utils.js';
+import { stringForUserExplicit } from 'lib/shared/user-utils.js';
// Native regex is a little bit different than web one as
// there are no named capturing groups yet on native.
@@ -8,4 +17,58 @@
`((^(.|\n)*\\s+)|^)@(${oldValidUsernameRegexString})?$`,
);
-export { nativeTypeaheadRegex };
+export type TypeaheadTooltipActionsParams<SuggestionItemType> = {
+ +suggestions: $ReadOnlyArray<SuggestionItemType>,
+ +textBeforeAtSymbol: string,
+ +text: string,
+ +query: string,
+ +focusAndUpdateTextAndSelection: (text: string, selection: Selection) => void,
+};
+function mentionTypeaheadTooltipActions({
+ suggestions,
+ textBeforeAtSymbol,
+ text,
+ query,
+ focusAndUpdateTextAndSelection,
+}: TypeaheadTooltipActionsParams<TypeaheadSuggestionItem>): $ReadOnlyArray<
+ TypeaheadTooltipActionItem<TypeaheadSuggestionItem>,
+> {
+ const actions = [];
+ for (const suggestion of suggestions) {
+ if (suggestion.type === 'user') {
+ const { userInfo } = suggestion;
+ const resolvedUsername = stringForUserExplicit(userInfo);
+ if (resolvedUsername === 'anonymous') {
+ continue;
+ }
+ const mentionText = `@${resolvedUsername}`;
+ actions.push({
+ key: userInfo.id,
+ execute: () => {
+ const { newText, newSelectionStart } = getNewTextAndSelection(
+ textBeforeAtSymbol,
+ text,
+ query,
+ mentionText,
+ );
+ focusAndUpdateTextAndSelection(newText, {
+ start: newSelectionStart,
+ end: newSelectionStart,
+ });
+ },
+ actionButtonContent: {
+ type: 'user',
+ userInfo,
+ },
+ });
+ }
+ }
+ return actions;
+}
+
+export type TypeaheadTooltipButtonComponentType<SuggestionItemType> =
+ React.ComponentType<{
+ +item: TypeaheadTooltipActionItem<SuggestionItemType>,
+ }>;
+
+export { nativeTypeaheadRegex, mentionTypeaheadTooltipActions };
diff --git a/web/chat/chat-input-bar.react.js b/web/chat/chat-input-bar.react.js
--- a/web/chat/chat-input-bar.react.js
+++ b/web/chat/chat-input-bar.react.js
@@ -631,7 +631,7 @@
props.inputState.typeaheadState.frozenMentionsCandidates,
viewerID,
typeaheadMatchedStrings.query,
- );
+ ).map(suggestion => suggestion.userInfo);
}, [
userSearchIndex,
props.inputState.typeaheadState.frozenMentionsCandidates,
diff --git a/web/utils/typeahead-utils.js b/web/utils/typeahead-utils.js
--- a/web/utils/typeahead-utils.js
+++ b/web/utils/typeahead-utils.js
@@ -107,7 +107,7 @@
textBeforeAtSymbol,
inputStateDraft,
query,
- suggestedUser,
+ stringForUserExplicit(suggestedUser),
);
inputStateSetDraft(newText);

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 1, 12:51 AM (21 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2603032
Default Alt Text
D8897.id31500.diff (13 KB)

Event Timeline