From f0c5a226d4ae02cd92d92372708056f63d3cd976 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Thu, 29 Apr 2021 16:32:30 -0400 Subject: added ParsedText, initial work for typeahead --- src/components/common/TaggTypeahead.tsx | 25 +++++++++++++++++++++++++ src/components/common/index.ts | 1 + 2 files changed, 26 insertions(+) create mode 100644 src/components/common/TaggTypeahead.tsx (limited to 'src/components/common') diff --git a/src/components/common/TaggTypeahead.tsx b/src/components/common/TaggTypeahead.tsx new file mode 100644 index 00000000..8a68c9a5 --- /dev/null +++ b/src/components/common/TaggTypeahead.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {StyleSheet, Text, View} from 'react-native'; +import {SCREEN_WIDTH} from '../../utils'; + +type TaggTypeaheadProps = { + search: string; +}; + +const TaggTypeahead: React.FC = ({search}) => { + return ( + + searching for {search} + + ); +}; + +const styles = StyleSheet.create({ + container: { + width: SCREEN_WIDTH * 0.9, + height: 100, + borderWidth: 1, + }, +}); + +export default TaggTypeahead; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 802cf505..4a49339b 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -24,3 +24,4 @@ export {default as TaggSquareButton} from './TaggSquareButton'; export {default as GradientBorderButton} from './GradientBorderButton'; export {default as BasicButton} from './BasicButton'; export {default as Avatar} from './Avatar'; +export {default as TaggTypeahead} from './TaggTypeahead'; -- cgit v1.2.3-70-g09d2 From bdc7186d869ff969c3e19cb9a4a8839df51a7f32 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Thu, 29 Apr 2021 19:34:10 -0400 Subject: completed typeahead missed import --- src/components/common/TaggTypeahead.tsx | 57 ++++++++++++++++++++++++++----- src/components/common/TaggUserRowCell.tsx | 55 +++++++++++++++++++++++++++++ src/components/common/index.ts | 1 + 3 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 src/components/common/TaggUserRowCell.tsx (limited to 'src/components/common') diff --git a/src/components/common/TaggTypeahead.tsx b/src/components/common/TaggTypeahead.tsx index 8a68c9a5..6239971e 100644 --- a/src/components/common/TaggTypeahead.tsx +++ b/src/components/common/TaggTypeahead.tsx @@ -1,23 +1,64 @@ -import React from 'react'; -import {StyleSheet, Text, View} from 'react-native'; +import React, {Fragment, useEffect, useState} from 'react'; +import {ScrollView, StyleSheet} from 'react-native'; +import {SEARCH_ENDPOINT_MESSAGES} from '../../constants'; +import {loadSearchResults} from '../../services'; +import {ProfilePreviewType} from '../../types'; import {SCREEN_WIDTH} from '../../utils'; +import TaggUserRowCell from './TaggUserRowCell'; type TaggTypeaheadProps = { - search: string; + query: string; + setSelectedMention: (user: ProfilePreviewType) => void; }; -const TaggTypeahead: React.FC = ({search}) => { +const TaggTypeahead: React.FC = ({ + query, + setSelectedMention, +}) => { + const [results, setResults] = useState([]); + + useEffect(() => { + getQuerySuggested(); + }, [query]); + + const getQuerySuggested = async () => { + if (query.length < 3) { + setResults([]); + return; + } + const searchResults = await loadSearchResults( + `${SEARCH_ENDPOINT_MESSAGES}?query=${query}`, + ); + if (searchResults && searchResults.users) { + setResults(searchResults.users); + } + }; + + if (results.length === 0) { + return ; + } + return ( - - searching for {search} - + + {results.map((user) => ( + { + setSelectedMention(user); + setResults([]); + }} + user={user} + /> + ))} + ); }; const styles = StyleSheet.create({ container: { + marginLeft: SCREEN_WIDTH * 0.05, width: SCREEN_WIDTH * 0.9, - height: 100, + maxHeight: 300, + borderRadius: 10, borderWidth: 1, }, }); diff --git a/src/components/common/TaggUserRowCell.tsx b/src/components/common/TaggUserRowCell.tsx new file mode 100644 index 00000000..446f5e87 --- /dev/null +++ b/src/components/common/TaggUserRowCell.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native'; +import {ProfilePreviewType} from '../../types'; +import {defaultUserProfile, normalize} from '../../utils'; + +type TaggUserRowCellProps = { + onPress: () => void; + user: ProfilePreviewType; +}; +const TaggUserRowCell: React.FC = ({onPress, user}) => { + return ( + + + + {`@${user.username}`} + + {user.first_name} {user.last_name} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + paddingHorizontal: 25, + paddingVertical: 15, + width: '100%', + }, + image: { + width: normalize(30), + height: normalize(30), + borderRadius: 30, + }, + textContent: { + flexDirection: 'column', + justifyContent: 'space-between', + marginLeft: 20, + }, + username: { + fontWeight: '500', + fontSize: normalize(14), + }, + name: { + fontWeight: '500', + fontSize: normalize(12), + color: '#828282', + }, +}); +export default TaggUserRowCell; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 4a49339b..b38056c6 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -25,3 +25,4 @@ export {default as GradientBorderButton} from './GradientBorderButton'; export {default as BasicButton} from './BasicButton'; export {default as Avatar} from './Avatar'; export {default as TaggTypeahead} from './TaggTypeahead'; +export {default as TaggUserRowCell} from './TaggUserRowCell'; -- cgit v1.2.3-70-g09d2 From a665c527cf0ab24b1a19b2ed35f38b9acb79cdeb Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Fri, 30 Apr 2021 15:16:32 -0400 Subject: removed old code, added controlled-mention --- package.json | 3 +- src/components/comments/AddComment.tsx | 83 +++++++-------------------------- src/components/common/TaggTypeahead.tsx | 37 +++++++++------ 3 files changed, 41 insertions(+), 82 deletions(-) (limited to 'src/components/common') diff --git a/package.json b/package.json index b3d69c76..d3c02059 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-native-animatable": "^1.3.3", "react-native-confirmation-code-field": "^6.5.0", "react-native-contacts": "^6.0.4", + "react-native-controlled-mentions": "^2.2.5", "react-native-date-picker": "^3.2.5", "react-native-device-info": "^7.3.1", "react-native-document-picker": "^5.0.3", @@ -102,4 +103,4 @@ "./node_modules/react-native-gesture-handler/jestSetup.js" ] } -} +} \ No newline at end of file diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 97c87299..7576675e 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -7,15 +7,14 @@ import { TextInput, View, } from 'react-native'; +import {MentionInput} from 'react-native-controlled-mentions'; import {TouchableOpacity} from 'react-native-gesture-handler'; -import ParsedText, {ParseShape} from 'react-native-parsed-text'; import {useDispatch, useSelector} from 'react-redux'; import UpArrowIcon from '../../assets/icons/up_arrow.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {postComment} from '../../services'; import {updateReplyPosted} from '../../store/actions'; import {RootState} from '../../store/rootreducer'; -import {ProfilePreviewType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import {Avatar, TaggTypeahead} from '../common'; @@ -40,14 +39,9 @@ const AddComment: React.FC = ({ }) => { const [comment, setComment] = useState(''); const [keyboardVisible, setKeyboardVisible] = useState(false); - const [isMentioning, setIsMentioning] = useState(false); - const [mentionQuery, setMentionQuery] = useState(''); - const [selectedMention, setSelectedMention] = useState(); - const [mentions, setMentions] = useState([]); const {avatar} = useSelector((state: RootState) => state.user); const dispatch = useDispatch(); const ref = useRef(null); - const [parsePatterns, setParsePatterns] = useState([]); const addComment = async () => { const trimmed = comment.trim(); @@ -78,30 +72,6 @@ const AddComment: React.FC = ({ } }; - useEffect(() => { - setParsePatterns( - mentions.map((m) => ({ - pattern: new RegExp(`@${m.username}`), - style: {color: TAGG_LIGHT_BLUE}, - })), - ); - }, [mentions]); - - useEffect(() => { - if (selectedMention) { - setComment( - comment.replace( - new RegExp(`@${mentionQuery}`), - `@${selectedMention.username} `, - ), - ); - setMentions([...mentions, selectedMention]); - setSelectedMention(undefined); - setMentionQuery(''); - setIsMentioning(false); - } - }, [selectedMention]); - useEffect(() => { const showKeyboard = () => setKeyboardVisible(true); Keyboard.addListener('keyboardWillShow', showKeyboard); @@ -125,12 +95,6 @@ const AddComment: React.FC = ({ - {isMentioning && ( - - )} = ({ ]}> - { - const newestChar = newText[newText.length - 1]; - const deletedChar = - newText.length === comment.length - 1 - ? comment[comment.length - 1] - : undefined; - if (newestChar === ' ' || deletedChar === '@') { - setIsMentioning(false); - setMentionQuery(''); - } - if (newestChar === '@') { - setIsMentioning(true); - } - if (isMentioning) { - const match = newText.match(/.*@(.*)$/); - if (match) { - setMentionQuery(match[1]); - } - } - setComment(newText); - }} - multiline={true} - ref={ref}> - - {comment} - - + value={comment} + onChange={setComment} + inputRef={ref} + partTypes={[ + { + trigger: '@', + renderSuggestions: (props) => , + allowedSpacesCount: 0, + isInsertSpaceAfterMention: true, + textStyle: {color: TAGG_LIGHT_BLUE}, + }, + ]} + /> diff --git a/src/components/common/TaggTypeahead.tsx b/src/components/common/TaggTypeahead.tsx index 6239971e..7cd99278 100644 --- a/src/components/common/TaggTypeahead.tsx +++ b/src/components/common/TaggTypeahead.tsx @@ -1,33 +1,30 @@ import React, {Fragment, useEffect, useState} from 'react'; import {ScrollView, StyleSheet} from 'react-native'; +import {MentionSuggestionsProps} from 'react-native-controlled-mentions'; import {SEARCH_ENDPOINT_MESSAGES} from '../../constants'; import {loadSearchResults} from '../../services'; import {ProfilePreviewType} from '../../types'; import {SCREEN_WIDTH} from '../../utils'; import TaggUserRowCell from './TaggUserRowCell'; -type TaggTypeaheadProps = { - query: string; - setSelectedMention: (user: ProfilePreviewType) => void; -}; - -const TaggTypeahead: React.FC = ({ - query, - setSelectedMention, +const TaggTypeahead: React.FC = ({ + keyword, + onSuggestionPress, }) => { const [results, setResults] = useState([]); + const [height, setHeight] = useState(0); useEffect(() => { getQuerySuggested(); - }, [query]); + }, [keyword]); const getQuerySuggested = async () => { - if (query.length < 3) { + if (!keyword || keyword.length < 3) { setResults([]); return; } const searchResults = await loadSearchResults( - `${SEARCH_ENDPOINT_MESSAGES}?query=${query}`, + `${SEARCH_ENDPOINT_MESSAGES}?query=${keyword}`, ); if (searchResults && searchResults.users) { setResults(searchResults.users); @@ -39,11 +36,19 @@ const TaggTypeahead: React.FC = ({ } return ( - + { + setHeight(event.nativeEvent.layout.height); + }}> {results.map((user) => ( { - setSelectedMention(user); + onSuggestionPress({ + id: user.id, + name: user.username, + }); setResults([]); }} user={user} @@ -57,8 +62,12 @@ const styles = StyleSheet.create({ container: { marginLeft: SCREEN_WIDTH * 0.05, width: SCREEN_WIDTH * 0.9, - maxHeight: 300, + maxHeight: 264, borderRadius: 10, + backgroundColor: 'white', + position: 'absolute', + alignSelf: 'center', + zIndex: 1, borderWidth: 1, }, }); -- cgit v1.2.3-70-g09d2 From 9795083f41e6417c32d9c070ab0b2bf4aca67012 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Tue, 4 May 2021 17:12:22 -0400 Subject: fixed merge issues --- src/components/common/TaggUserRowCell.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src/components/common') diff --git a/src/components/common/TaggUserRowCell.tsx b/src/components/common/TaggUserRowCell.tsx index 446f5e87..446dedc9 100644 --- a/src/components/common/TaggUserRowCell.tsx +++ b/src/components/common/TaggUserRowCell.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native'; +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; import {ProfilePreviewType} from '../../types'; -import {defaultUserProfile, normalize} from '../../utils'; +import {normalize} from '../../utils'; +import Avatar from './Avatar'; type TaggUserRowCellProps = { onPress: () => void; @@ -10,11 +11,7 @@ type TaggUserRowCellProps = { const TaggUserRowCell: React.FC = ({onPress, user}) => { return ( - + {`@${user.username}`} -- cgit v1.2.3-70-g09d2