Skip to content

Commit 96bd43d

Browse files
committed
chore: added edit feature to mentioned message
1 parent 507a1af commit 96bd43d

File tree

8 files changed

+112
-24
lines changed

8 files changed

+112
-24
lines changed

packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelInput/EditInput.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ const EditInput = forwardRef<RNTextInput, EditInputProps>(function EditInput(
5050

5151
const onPressSave = () => {
5252
if (editMessage.isUserMessage()) {
53-
onUpdateUserMessage(text, editMessage).catch(() => toast.show(STRINGS.TOAST.UPDATE_MSG_ERROR, 'error'));
53+
const mention = {
54+
userIds: mentionedUsers.map((it) => it.user.userId),
55+
messageTemplate: mentionManager.textToMentionedMessageTemplate(text, mentionedUsers),
56+
};
57+
onUpdateUserMessage(text, editMessage, mention).catch(() => toast.show(STRINGS.TOAST.UPDATE_MSG_ERROR, 'error'));
5458
}
5559
setEditMessage();
5660
onChangeText('');

packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelInput/SendInput.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
5858
const toast = useToast();
5959

6060
const onPressSend = () => {
61-
onSendUserMessage(text).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
61+
const mention = {
62+
userIds: mentionedUsers.map((it) => it.user.userId),
63+
messageTemplate: mentionManager.textToMentionedMessageTemplate(text, mentionedUsers),
64+
};
65+
onSendUserMessage(text, mention).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
6266
onChangeText('');
6367
};
6468

packages/uikit-react-native/src/domain/groupChannel/component/GroupChannelInput/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import SendInput from './SendInput';
2222
const AUTO_FOCUS = Platform.select({ ios: false, android: true, default: false });
2323
const KEYBOARD_AVOID_VIEW_BEHAVIOR = Platform.select({ ios: 'padding' as const, default: undefined });
2424

25+
// TODO: Refactor 'Edit' mode to clearly
2526
const GroupChannelInput = (props: GroupChannelProps['Input']) => {
2627
const { top, left, right, bottom } = useSafeAreaInsets();
2728
const { colors } = useUIKitTheme();
@@ -39,11 +40,11 @@ const GroupChannelInput = (props: GroupChannelProps['Input']) => {
3940
const [inputHeight, setInputHeight] = useState(styles.inputDefault.height);
4041

4142
const { selection, setSelection, onSelectionChange, textInputRef, text, onChangeText, mentionedUsers } =
42-
useMentionTextInput();
43+
useMentionTextInput({ editMessage });
4344

4445
useTypingTrigger(text, channel);
4546
useTextPersistenceOnDisabled(text, onChangeText, chatAvailableState.disabled);
46-
useAutoFocusOnEditMode(textInputRef, onChangeText, editMessage);
47+
useAutoFocusOnEditMode(textInputRef, editMessage);
4748

4849
const onPressToMention: GroupChannelProps['MentionSuggestionList']['onPressToMention'] = (
4950
user,
@@ -147,12 +148,10 @@ const useTextPersistenceOnDisabled = (text: string, setText: (val: string) => vo
147148

148149
const useAutoFocusOnEditMode = (
149150
textInputRef: MutableRefObject<TextInput | undefined>,
150-
setText: (val: string) => void,
151151
editMessage?: SendbirdUserMessage | SendbirdFileMessage,
152152
) => {
153153
useEffect(() => {
154154
if (editMessage?.isUserMessage()) {
155-
setText(editMessage.message ?? '');
156155
if (!AUTO_FOCUS) setTimeout(() => textInputRef.current?.focus(), 500);
157156
}
158157
}, [editMessage]);

packages/uikit-react-native/src/domain/groupChannel/types.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import type {
1616
import type { FileType } from '../../platform/types';
1717
import type { CommonComponent, MentionedUser, Range } from '../../types';
1818

19+
type UserMessageMentionParams = {
20+
messageTemplate: SendbirdUserMessageCreateParams['mentionedMessageTemplate'];
21+
userIds: SendbirdUserMessageCreateParams['mentionedUserIds'];
22+
};
23+
1924
export interface GroupChannelProps {
2025
Fragment: {
2126
channel: SendbirdGroupChannel;
@@ -92,9 +97,13 @@ export interface GroupChannelProps {
9297
Input: {
9398
shouldRenderInput: boolean;
9499
onSendFileMessage: (file: FileType) => Promise<void>;
95-
onSendUserMessage: (text: string) => Promise<void>;
100+
onSendUserMessage: (text: string, mention?: UserMessageMentionParams) => Promise<void>;
96101
onUpdateFileMessage: (editedFile: FileType, message: SendbirdFileMessage) => Promise<void>;
97-
onUpdateUserMessage: (editedText: string, message: SendbirdUserMessage) => Promise<void>;
102+
onUpdateUserMessage: (
103+
editedText: string,
104+
message: SendbirdUserMessage,
105+
mention?: UserMessageMentionParams,
106+
) => Promise<void>;
98107
MentionSuggestionList: (props: GroupChannelProps['MentionSuggestionList']) => JSX.Element | null;
99108
};
100109

packages/uikit-react-native/src/fragments/createGroupChannelFragment.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,29 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
7575
const processedParams = await onBeforeSendFileMessage({ file });
7676
await sendFileMessage(processedParams);
7777
});
78-
const onSendUserMessage: GroupChannelProps['Input']['onSendUserMessage'] = useFreshCallback(async (text) => {
79-
const processedParams = await onBeforeSendUserMessage({ message: text });
80-
await sendUserMessage(processedParams);
81-
});
78+
const onSendUserMessage: GroupChannelProps['Input']['onSendUserMessage'] = useFreshCallback(
79+
async (text, mention) => {
80+
const processedParams = await onBeforeSendUserMessage({
81+
message: text,
82+
mentionedUserIds: mention?.userIds,
83+
mentionedMessageTemplate: mention?.messageTemplate,
84+
});
85+
await sendUserMessage(processedParams);
86+
},
87+
);
8288
const onUpdateFileMessage: GroupChannelProps['Input']['onUpdateFileMessage'] = useFreshCallback(
8389
async (editedFile, message) => {
8490
const processedParams = await onBeforeSendFileMessage({ file: editedFile });
8591
await updateFileMessage(message.messageId, processedParams);
8692
},
8793
);
8894
const onUpdateUserMessage: GroupChannelProps['Input']['onUpdateUserMessage'] = useFreshCallback(
89-
async (editedText, message) => {
90-
const processedParams = await onBeforeSendUserMessage({ message: editedText });
95+
async (editedText, message, mention) => {
96+
const processedParams = await onBeforeSendUserMessage({
97+
message: editedText,
98+
mentionedUserIds: mention?.userIds,
99+
mentionedMessageTemplate: mention?.messageTemplate,
100+
});
91101
await updateUserMessage(message.messageId, processedParams);
92102
},
93103
);

packages/uikit-react-native/src/hooks/useMentionTextInput.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { useCallback, useRef, useState } from 'react';
1+
import { useCallback, useEffect, useRef, useState } from 'react';
22
import type { NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData } from 'react-native';
33

4-
import { replace, useFreshCallback } from '@sendbird/uikit-utils';
4+
import { SendbirdFileMessage, SendbirdUserMessage, replace, useFreshCallback } from '@sendbird/uikit-utils';
55

66
import type { MentionedUser, Range } from '../types';
77
import { useSendbirdChat } from './useContext';
88

9-
const useMentionTextInput = () => {
9+
const useMentionTextInput = (params: { editMessage?: SendbirdUserMessage | SendbirdFileMessage }) => {
1010
const { mentionManager } = useSendbirdChat();
1111

1212
const mentionedUsersRef = useRef<MentionedUser[]>([]);
@@ -15,6 +15,28 @@ const useMentionTextInput = () => {
1515
const [text, setText] = useState('');
1616
const [selection, setSelection] = useState({ start: 0, end: 0 });
1717

18+
// TODO: Refactor text edit logic more clearly
19+
useEffect(() => {
20+
if (
21+
params.editMessage?.mentionedMessageTemplate &&
22+
params.editMessage?.mentionedUsers &&
23+
mentionManager.mentionEnabled
24+
) {
25+
const result = mentionManager.templateToTextAndMentionedUsers(
26+
params.editMessage.mentionedMessageTemplate,
27+
params.editMessage.mentionedUsers,
28+
);
29+
30+
mentionedUsersRef.current = result.mentionedUsers;
31+
setText(result.mentionedText);
32+
} else {
33+
mentionedUsersRef.current = [];
34+
if (params.editMessage?.isUserMessage()) {
35+
setText(params.editMessage.message);
36+
}
37+
}
38+
}, [params.editMessage]);
39+
1840
const onChangeText = useFreshCallback((_nextText: string, addedMentionedUser?: MentionedUser) => {
1941
const prevText = text;
2042
let nextText = _nextText;

packages/uikit-react-native/src/libs/MentionManager.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22

33
import { Text, createStyleSheet } from '@sendbird/uikit-react-native-foundation';
44
import type { SendbirdUser } from '@sendbird/uikit-utils';
5-
import { createMentionTemplateRegex } from '@sendbird/uikit-utils';
5+
import { createMentionTemplateRegex, replaceWithRegex } from '@sendbird/uikit-utils';
66

77
import type { MentionedUser, Range } from '../types';
88
import type { MentionConfigInterface } from './MentionConfig';
@@ -171,9 +171,43 @@ class MentionManager {
171171
* @description Convert @{user.id} template to @user.nickname text and MentionedUser[] array.
172172
* */
173173
public templateToTextAndMentionedUsers = (template: string, mentionedUsers: SendbirdUser[]) => {
174+
const actualMentionedUsers: MentionedUser[] = [];
175+
176+
let offsetToMove = 0;
177+
const mentionedText = replaceWithRegex(
178+
template,
179+
this.templateRegex,
180+
({ match, matchIndex, groups }) => {
181+
const user = mentionedUsers.find((it) => it.userId === groups[2]);
182+
if (user && typeof matchIndex === 'number') {
183+
const userIdSpan = match;
184+
const userNicknameSpan = this.asMentionedMessageText(user);
185+
186+
const offsetAfterConverted = userNicknameSpan.length - userIdSpan.length;
187+
188+
const originalRange: Range = {
189+
start: matchIndex,
190+
end: matchIndex + userIdSpan.length,
191+
};
192+
193+
const convertedRange: Range = {
194+
start: Math.max(0, originalRange.start + offsetToMove),
195+
end: originalRange.end + offsetToMove + offsetAfterConverted,
196+
};
197+
198+
offsetToMove += offsetAfterConverted;
199+
200+
actualMentionedUsers.push({ range: convertedRange, user });
201+
return userNicknameSpan;
202+
}
203+
return match;
204+
},
205+
'',
206+
).join('');
207+
174208
return {
175-
mentionedText: '',
176-
mentionedUsers: [] as MentionedUser[],
209+
mentionedText,
210+
mentionedUsers: actualMentionedUsers,
177211
};
178212
};
179213
}

packages/uikit-utils/src/shared/regex.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,29 @@ export const createMentionTemplateRegex = (trigger: string) => new RegExp(`(${tr
3333
export const replaceWithRegex = <T>(
3434
text: string,
3535
regex: RegExp,
36-
replacer: (params: { match: string; groups: string[]; index: number; keyPrefix: string }) => T,
36+
replacer: (params: {
37+
match: string;
38+
groups: string[];
39+
matchIndex: number | undefined;
40+
index: number;
41+
keyPrefix: string;
42+
}) => T,
3743
keyPrefix: string,
3844
) => {
3945
const matches = [...text.matchAll(regex)];
4046
const founds = matches.map((value) => {
4147
const text = value[0];
4248
const start = value.index ?? 0;
4349
const end = start + text.length;
44-
return { text, start, end, groups: value };
50+
return { text, start, end, groups: value, matchIndex: value.index };
4551
});
4652

4753
const items: Array<T | string> = [text];
4854
let cursor = 0;
49-
founds.forEach(({ text, start, end, groups }, index) => {
55+
founds.forEach(({ text, start, end, groups, matchIndex }, index) => {
5056
const restText = items.pop() as string;
5157
const head = restText.slice(0, start - cursor);
52-
const mid = replacer({ match: text, groups, index, keyPrefix });
58+
const mid = replacer({ match: text, groups, matchIndex, index, keyPrefix });
5359
const tail = restText.slice(end - cursor);
5460
items.push(head, mid, tail);
5561
cursor = end;

0 commit comments

Comments
 (0)