aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-05-11 18:13:54 -0400
committerGitHub <noreply@github.com>2021-05-11 18:13:54 -0400
commit610b6c9ddd2414b3b0d5c4cc24c35ef6e9e68513 (patch)
tree466db58bf218687603e636181b3567e1b2de7f26
parent4569240e09ff134d49fa11180c0074329a3ac95b (diff)
parentd760f15aa6971c39f028e18a007a5c56f08b6149 (diff)
Merge pull request #413 from brian-tagg/mentionsCollapse
[TMA-847] Fixed issue with mentions not collapsing when deleting '@' too quickly
-rw-r--r--src/components/comments/AddComment.tsx4
-rw-r--r--src/components/comments/MentionInputControlled.tsx195
2 files changed, 197 insertions, 2 deletions
diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx
index 9cf10b5e..befaa8fe 100644
--- a/src/components/comments/AddComment.tsx
+++ b/src/components/comments/AddComment.tsx
@@ -7,7 +7,6 @@ import {
TextInput,
View,
} from 'react-native';
-import {MentionInput} from 'react-native-controlled-mentions';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {useDispatch, useSelector} from 'react-redux';
import UpArrowIcon from '../../assets/icons/up_arrow.svg';
@@ -20,6 +19,7 @@ import {CommentThreadType, CommentType} from '../../types';
import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils';
import {mentionPartTypes} from '../../utils/comments';
import {Avatar} from '../common';
+import {MentionInputControlled} from './MentionInputControlled';
export interface AddCommentProps {
momentId: string;
@@ -112,7 +112,7 @@ const AddComment: React.FC<AddCommentProps> = ({momentId, placeholderText}) => {
]}>
<View style={styles.textContainer}>
<Avatar style={styles.avatar} uri={avatar} />
- <MentionInput
+ <MentionInputControlled
containerStyle={styles.text}
placeholder={placeholderText}
value={inReplyToMention + comment}
diff --git a/src/components/comments/MentionInputControlled.tsx b/src/components/comments/MentionInputControlled.tsx
new file mode 100644
index 00000000..6abcb566
--- /dev/null
+++ b/src/components/comments/MentionInputControlled.tsx
@@ -0,0 +1,195 @@
+import React, {FC, MutableRefObject, useMemo, useRef, useState} from 'react';
+import {
+ NativeSyntheticEvent,
+ Text,
+ TextInput,
+ TextInputSelectionChangeEventData,
+ View,
+} from 'react-native';
+
+import {
+ MentionInputProps,
+ MentionPartType,
+ Suggestion,
+} from 'react-native-controlled-mentions/dist/types';
+import {
+ defaultMentionTextStyle,
+ generateValueFromPartsAndChangedText,
+ generateValueWithAddedSuggestion,
+ getMentionPartSuggestionKeywords,
+ isMentionPartType,
+ parseValue,
+} from 'react-native-controlled-mentions/dist/utils';
+
+const MentionInputControlled: FC<MentionInputProps> = ({
+ value,
+ onChange,
+
+ partTypes = [],
+
+ inputRef: propInputRef,
+
+ containerStyle,
+
+ onSelectionChange,
+
+ ...textInputProps
+}) => {
+ const textInput = useRef<TextInput | null>(null);
+
+ const [selection, setSelection] = useState({start: 0, end: 0});
+
+ const [keyboardText, setKeyboardText] = useState<string>('');
+
+ const validRegex = () => {
+ if (partTypes.length === 0) {
+ return /.*\@[^ ]*$/;
+ } else {
+ return new RegExp(`.*\@${keywordByTrigger[partTypes[0].trigger]}.*$`);
+ }
+ };
+
+ const {plainText, parts} = useMemo(() => parseValue(value, partTypes), [
+ value,
+ partTypes,
+ ]);
+
+ const handleSelectionChange = (
+ event: NativeSyntheticEvent<TextInputSelectionChangeEventData>,
+ ) => {
+ setSelection(event.nativeEvent.selection);
+
+ onSelectionChange && onSelectionChange(event);
+ };
+
+ /**
+ * Callback that trigger on TextInput text change
+ *
+ * @param changedText
+ */
+ const onChangeInput = (changedText: string) => {
+ setKeyboardText(changedText);
+ onChange(
+ generateValueFromPartsAndChangedText(parts, plainText, changedText),
+ );
+ };
+
+ /**
+ * We memoize the keyword to know should we show mention suggestions or not
+ */
+ const keywordByTrigger = useMemo(() => {
+ return getMentionPartSuggestionKeywords(
+ parts,
+ plainText,
+ selection,
+ partTypes,
+ );
+ }, [parts, plainText, selection, partTypes]);
+
+ /**
+ * Callback on mention suggestion press. We should:
+ * - Get updated value
+ * - Trigger onChange callback with new value
+ */
+ const onSuggestionPress = (mentionType: MentionPartType) => (
+ suggestion: Suggestion,
+ ) => {
+ const newValue = generateValueWithAddedSuggestion(
+ parts,
+ mentionType,
+ plainText,
+ selection,
+ suggestion,
+ );
+
+ if (!newValue) {
+ return;
+ }
+
+ onChange(newValue);
+
+ /**
+ * Move cursor to the end of just added mention starting from trigger string and including:
+ * - Length of trigger string
+ * - Length of mention name
+ * - Length of space after mention (1)
+ *
+ * Not working now due to the RN bug
+ */
+ // const newCursorPosition = currentPart.position.start + triggerPartIndex + trigger.length +
+ // suggestion.name.length + 1;
+
+ // textInput.current?.setNativeProps({selection: {start: newCursorPosition, end: newCursorPosition}});
+ };
+
+ const handleTextInputRef = (ref: TextInput) => {
+ textInput.current = ref as TextInput;
+
+ if (propInputRef) {
+ if (typeof propInputRef === 'function') {
+ propInputRef(ref);
+ } else {
+ (propInputRef as MutableRefObject<TextInput>).current = ref as TextInput;
+ }
+ }
+ };
+
+ const renderMentionSuggestions = (mentionType: MentionPartType) => (
+ <React.Fragment key={mentionType.trigger}>
+ {mentionType.renderSuggestions &&
+ mentionType.renderSuggestions({
+ keyword: keywordByTrigger[mentionType.trigger],
+ onSuggestionPress: onSuggestionPress(mentionType),
+ })}
+ </React.Fragment>
+ );
+
+ const validateInput = (testString: string) => {
+ return validRegex().test(testString);
+ };
+
+ return (
+ <View style={containerStyle}>
+ {validateInput(keyboardText)
+ ? (partTypes.filter(
+ (one) =>
+ isMentionPartType(one) &&
+ one.renderSuggestions != null &&
+ !one.isBottomMentionSuggestionsRender,
+ ) as MentionPartType[]).map(renderMentionSuggestions)
+ : null}
+
+ <TextInput
+ multiline
+ {...textInputProps}
+ ref={handleTextInputRef}
+ onChangeText={onChangeInput}
+ onSelectionChange={handleSelectionChange}>
+ <Text>
+ {parts.map(({text, partType, data}, index) =>
+ partType ? (
+ <Text
+ key={`${index}-${data?.trigger ?? 'pattern'}`}
+ style={partType.textStyle ?? defaultMentionTextStyle}>
+ {text}
+ </Text>
+ ) : (
+ <Text key={index}>{text}</Text>
+ ),
+ )}
+ </Text>
+ </TextInput>
+
+ {validateInput(keyboardText)
+ ? (partTypes.filter(
+ (one) =>
+ isMentionPartType(one) &&
+ one.renderSuggestions != null &&
+ one.isBottomMentionSuggestionsRender,
+ ) as MentionPartType[]).map(renderMentionSuggestions)
+ : null}
+ </View>
+ );
+};
+
+export {MentionInputControlled};