Skip to content
This repository has been archived by the owner on May 7, 2023. It is now read-only.

Commit

Permalink
feat: Ability to find and tag users in [Message] (#501)
Browse files Browse the repository at this point in the history
* fix : tag text proper position

* fix : user text match startswith -> includes

* fix : duplicated user type -> def type User

* fix : duplicated type -> use User type

* fix : ui func simplify

* refactor : input function name clearly onChangeSelection -> onChangeCursor

* refactor: proper nameing cursorFrontText -> cursorPrefix

* reafactor : renaming newMessage -> newMessageWithTag

* refactor : string type to TextInputKeyPressEventData

* fix: message set variable and splice

* fix : defaultProps onChangeCursor add arg type

* fix : defaultProps add message type

* feat : add taged user images

* refactor : typo seleteTagUser -> selectTagUser

* fix : tagUserListItem style func to number, tag users pop up close the message
  • Loading branch information
whywhyy authored Oct 23, 2021
1 parent 83152db commit 6058594
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 351 deletions.
1 change: 0 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@
"relay-compiler-language-typescript": "^14.2.1",
"relay-config": "^12.0.0",
"relay-test-utils": "^12.0.0",
"sharp-cli": "^1.15.0",
"ts-jest": "^27.0.5",
"typescript": "^4.4.3"
},
Expand Down
17 changes: 13 additions & 4 deletions client/src/__generated__/ChannelQuery.graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion client/src/__generated__/MessageComponent_message.graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions client/src/__generated__/MessagePaginationQuery.graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions client/src/__generated__/MessagesQuery.graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 97 additions & 2 deletions client/src/components/pages/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ListRenderItem,
Platform,
Text,
TextInputKeyPressEventData,
TouchableOpacity,
View,
} from 'react-native';
Expand Down Expand Up @@ -118,6 +119,7 @@ const messagesFragment = graphql`
id
name
nickname
photoURL
}
createdAt
...MessageListItem_message
Expand All @@ -133,13 +135,21 @@ const messagesFragment = graphql`
}
`;

export type User = {
readonly id: string;
readonly nickname: string | null;
readonly name: string | null;
readonly photoURL: string | null;
} | null;

interface MessageProp {
users: User[];
channelId: string;
messages: MessageComponent_message$key;
searchArgs: MessagesQueryVariables;
}

const MessagesFragment: FC<MessageProp> = ({channelId, messages}) => {
const MessagesFragment: FC<MessageProp> = ({channelId, messages, users}) => {
const {theme} = useTheme();
const navigation = useNavigation<RootStackNavigationProps>();
const insets = useSafeAreaInsets();
Expand Down Expand Up @@ -172,6 +182,18 @@ const MessagesFragment: FC<MessageProp> = ({channelId, messages}) => {
};

const [message, setMessage] = useState<string>('');

const [cursor, setCursor] = useState<{start: number; end: number}>({
start: 0,
end: 0,
});

const [lastKeyEvent, setLastKeyEvent] = useState<TextInputKeyPressEventData>({
key: '',
});

const [tagUsers, setTagUsers] = useState<User[]>([]);

const [isImageUploading, setIsImageUploading] = useState<boolean>(false);
const {showModal} = useProfileContext();

Expand Down Expand Up @@ -363,8 +385,69 @@ const MessagesFragment: FC<MessageProp> = ({channelId, messages}) => {
);
};

const changeMessageByTagUser = (item: User): void => {
const cursorPrefix = message.slice(0, cursor.start + 1);
const tagIdx = cursorPrefix.lastIndexOf('@');

const newMessageWithTag = [...message];

newMessageWithTag.splice(
tagIdx + 1,
cursor.start - tagIdx - 1,
item?.name + ' ' || '',
);

setMessage(newMessageWithTag.join(''));
};

const selectTagUser = (item: User): void => {
setTagUsers([]);
changeMessageByTagUser(item);
};

const parseTagText = (
inputedText: string,
): {isTag: boolean; parsedText: string} => {
let result = {isTag: false, parsedText: ''};

const cursorPrefix = inputedText.slice(0, cursor.start + 1);
const tagIdx = cursorPrefix.lastIndexOf('@');
if (tagIdx !== -1)
if (tagIdx === 0 || (tagIdx > 0 && inputedText[tagIdx - 1] === ' '))
if (lastKeyEvent.key === 'Backspace')
result = {
isTag: true,
parsedText: cursorPrefix.slice(tagIdx + 1, -2),
};
else result = {isTag: true, parsedText: cursorPrefix.slice(tagIdx + 1)};

return result;
};

const getTagUsersByText = (inputedText: string): User[] => {
let result = [];
if (inputedText === '') result = users;
else
result = users.filter((item) => {
return item?.name?.includes(inputedText);
});

return result;
};

const handleTagUsers = (inputedText: string): void => {
const parsedTagText = parseTagText(inputedText);

const paredUsers = parsedTagText.isTag
? getTagUsersByText(parsedTagText.parsedText)
: [];
setTagUsers(paredUsers);
};

return (
<GiftedChat
selectTagUser={selectTagUser}
tagUsers={tagUsers}
messages={nodes}
borderColor={theme.disabled}
onEndReached={onEndReached}
Expand All @@ -377,9 +460,17 @@ const MessagesFragment: FC<MessageProp> = ({channelId, messages}) => {
message={message}
placeholder={getString('WRITE_MESSAGE')}
onChangeMessage={(text) => {
if (text === '\n') return setMessage('');
if (text === '\n') {
setMessage('');

return handleTagUsers('');
}

setMessage(text);
handleTagUsers(text);
}}
onChangeCursor={({start, end}) => {
setCursor({start: start, end: end});
}}
renderItem={renderItem}
keyExtractor={(item) => item.id || nanoid()}
Expand All @@ -393,6 +484,9 @@ const MessagesFragment: FC<MessageProp> = ({channelId, messages}) => {

setMessage(`${message}\n`);
}

setLastKeyEvent(nativeEvent);
handleTagUsers(message);
}}
openedOptionView={
<SvgArrDown width={18} height={18} stroke={theme.text} />
Expand Down Expand Up @@ -506,6 +600,7 @@ const ContentContainer: FC<ContentProps> = ({searchArgs, channelId}) => {

return (
<MessagesFragment
users={users}
channelId={channelId}
messages={messagesQueryResponse}
searchArgs={searchArgs}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ exports[`[Message] rendering test renders as expected 1`] = `
"cacheConfig": Object {
"force": true,
},
"identifier": "ba62a2ee05d85c0c62ab358d86a737db{\\"after\\":null,\\"channelId\\":\\"abcdef\\",\\"first\\":20,\\"searchText\\":null}",
"identifier": "1ff7872b58757bca9b62f46b886f16b8{\\"after\\":null,\\"channelId\\":\\"abcdef\\",\\"first\\":20,\\"searchText\\":null}",
"node": Object {
"fragment": Object {
"abstractKey": null,
Expand Down Expand Up @@ -283,14 +283,14 @@ exports[`[Message] rendering test renders as expected 1`] = `
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "thumbURL",
"name": "photoURL",
"storageKey": null,
},
Object {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "photoURL",
"name": "thumbURL",
"storageKey": null,
},
Object {
Expand Down Expand Up @@ -429,7 +429,7 @@ exports[`[Message] rendering test renders as expected 1`] = `
],
},
"params": Object {
"cacheID": "ba62a2ee05d85c0c62ab358d86a737db",
"cacheID": "1ff7872b58757bca9b62f46b886f16b8",
"id": null,
"metadata": Object {},
"name": "MessagesQuery",
Expand All @@ -456,6 +456,7 @@ fragment MessageComponent_message_WlZsr on Query {
id
name
nickname
photoURL
}
createdAt
...MessageListItem_message
Expand Down Expand Up @@ -522,6 +523,7 @@ fragment ProfileModal_user on User {
"id": "test-user-111",
"name": "John Doe",
"nickname": "john",
"photoURL": "<mock-value-for-field-\\"photoURL\\">",
},
"text": "Hello there!",
},
Expand Down Expand Up @@ -799,6 +801,7 @@ fragment ProfileModal_user on User {
onChangeText={[Function]}
onFocus={[Function]}
onKeyPress={[Function]}
onSelectionChange={[Function]}
placeholder="Write message..."
rejectResponderTermination={true}
returnKeyType="done"
Expand Down
Loading

0 comments on commit 6058594

Please sign in to comment.