Skip to content

Commit 4ddb91e

Browse files
committed
Add handling for short mentions WIP
1 parent 3753ea9 commit 4ddb91e

File tree

4 files changed

+216
-89
lines changed

4 files changed

+216
-89
lines changed

src/components/RNMarkdownTextInput.tsx

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import type {MarkdownRange, MarkdownTextInputProps} from '@expensify/react-native-live-markdown';
1+
import type {MarkdownTextInputProps} from '@expensify/react-native-live-markdown';
22
import {MarkdownTextInput, parseExpensiMark} from '@expensify/react-native-live-markdown';
33
import type {ForwardedRef} from 'react';
4-
import React, {forwardRef, useCallback} from 'react';
5-
import Animated from 'react-native-reanimated';
4+
import React, {forwardRef, useCallback, useEffect, useMemo} from 'react';
5+
import Animated, {useSharedValue} from 'react-native-reanimated';
66
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
77
import useTheme from '@hooks/useTheme';
8+
import {decorateRangesWithShortMentions} from '@libs/ParsingUtils';
89
import CONST from '@src/CONST';
10+
import {usePersonalDetails} from './OnyxProvider';
911

1012
// Convert the underlying TextInput into an Animated component so that we can take an animated ref and pass it to a worklet
1113
const AnimatedMarkdownTextInput = Animated.createAnimatedComponent(MarkdownTextInput);
@@ -17,30 +19,40 @@ type RNMarkdownTextInputWithRefProps = Omit<MarkdownTextInputProps, 'parser'> &
1719
parser?: MarkdownTextInputProps['parser'];
1820
};
1921

20-
function decorateRangesWithCurrentUser(ranges: MarkdownRange[], text: string, currentUser: string): MarkdownRange[] {
21-
'worklet';
22-
23-
return ranges.map((range) => {
24-
if (range.type === 'mention-user') {
25-
const mentionText = text.slice(range.start, range.start + range.length);
26-
const isCurrentUser = mentionText === `@${currentUser}`;
27-
if (isCurrentUser) {
28-
return {
29-
...range,
30-
type: 'mention-here',
31-
};
32-
}
33-
}
34-
35-
return range;
36-
});
37-
}
38-
3922
function RNMarkdownTextInputWithRef({maxLength, parser, ...props}: RNMarkdownTextInputWithRefProps, ref: ForwardedRef<AnimatedMarkdownTextInputRef>) {
4023
const theme = useTheme();
4124
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
4225

43-
const currentUserLogin = currentUserPersonalDetails.login;
26+
const personalDetails = usePersonalDetails();
27+
const mentionsListSV = useSharedValue<string[]>([]);
28+
29+
useEffect(() => {
30+
if (!personalDetails) {
31+
return;
32+
}
33+
34+
const mentionsList = Object.values(personalDetails)
35+
.map((personalDetail) => {
36+
if (!personalDetail?.login) {
37+
return;
38+
}
39+
40+
const [username] = personalDetail.login.split('@');
41+
return username ? `@${username}` : undefined;
42+
})
43+
.filter((login): login is string => !!login);
44+
45+
mentionsListSV.set(mentionsList);
46+
}, [mentionsListSV, personalDetails]);
47+
48+
const currentUserLogin = useMemo(() => {
49+
if (!currentUserPersonalDetails.login) {
50+
return;
51+
}
52+
53+
const [baseName] = currentUserPersonalDetails.login.split('@');
54+
return `@${baseName}`;
55+
}, [currentUserPersonalDetails.login]);
4456

4557
// We accept parser passed down as a prop or use ExpensiMark if parser is not defined
4658
const parserWorklet = useCallback(
@@ -56,9 +68,9 @@ function RNMarkdownTextInputWithRef({maxLength, parser, ...props}: RNMarkdownTex
5668
return parsedMentions;
5769
}
5870

59-
return decorateRangesWithCurrentUser(parsedMentions, text, currentUserLogin);
71+
return decorateRangesWithShortMentions(parsedMentions, text, mentionsListSV.get(), currentUserLogin);
6072
},
61-
[currentUserLogin, parser],
73+
[currentUserLogin, mentionsListSV, parser],
6274
);
6375

6476
return (
@@ -86,5 +98,4 @@ function RNMarkdownTextInputWithRef({maxLength, parser, ...props}: RNMarkdownTex
8698
RNMarkdownTextInputWithRef.displayName = 'RNTextInputWithRef';
8799

88100
export default forwardRef(RNMarkdownTextInputWithRef);
89-
export {decorateRangesWithCurrentUser};
90101
export type {AnimatedMarkdownTextInputRef};

src/libs/ParsingUtils.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type {MarkdownRange} from '@expensify/react-native-live-markdown';
2+
3+
/**
4+
* returns ranges Fixme
5+
*/
6+
function decorateRangesWithShortMentions(ranges: MarkdownRange[], text: string, availableMentions: string[], currentUser: string): MarkdownRange[] {
7+
'worklet';
8+
9+
return ranges
10+
.map((range) => {
11+
if (range.type === 'mention-short') {
12+
const mentionText = text.slice(range.start, range.start + range.length);
13+
14+
if (mentionText === currentUser) {
15+
return {
16+
...range,
17+
type: 'mention-here',
18+
};
19+
}
20+
21+
if (availableMentions.includes(mentionText)) {
22+
return {
23+
...range,
24+
type: 'mention-user',
25+
};
26+
}
27+
28+
// If it's neither, then we remove the range since no special styling will be needed
29+
return;
30+
}
31+
return range;
32+
})
33+
.filter((maybeRange): maybeRange is MarkdownRange => !!maybeRange);
34+
}
35+
36+
// eslint-disable-next-line import/prefer-default-export
37+
export {decorateRangesWithShortMentions};

tests/unit/decorateRangesWithCurrentUserTest.ts

Lines changed: 0 additions & 63 deletions
This file was deleted.

tests/unit/libs/ParsingUtilsTest.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import type {MarkdownRange} from '@expensify/react-native-live-markdown';
2+
import {decorateRangesWithShortMentions} from '@libs/ParsingUtils';
3+
4+
describe('decorateRangesWithShortMentions', () => {
5+
test('returns empty list for empty text', () => {
6+
const result = decorateRangesWithShortMentions([], '', [], '');
7+
expect(result).toEqual([]);
8+
});
9+
10+
test('returns empty list when there are no relevant mentions', () => {
11+
const text = 'Lorem ipsum';
12+
const result = decorateRangesWithShortMentions([], text, [], '');
13+
expect(result).toEqual([]);
14+
});
15+
16+
test('returns unchanged ranges when there are other markups than user-mentions', () => {
17+
const text = 'Lorem ipsum';
18+
const ranges: MarkdownRange[] = [
19+
{
20+
type: 'bold',
21+
start: 5,
22+
length: 3,
23+
},
24+
];
25+
const result = decorateRangesWithShortMentions(ranges, text, [], '');
26+
expect(result).toEqual([
27+
{
28+
type: 'bold',
29+
start: 5,
30+
length: 3,
31+
},
32+
]);
33+
});
34+
35+
test('returns ranges with current user type changed to "mention-here"', () => {
36+
const text = 'Lorem ipsum @myUser';
37+
const ranges: MarkdownRange[] = [
38+
{
39+
type: 'mention-short',
40+
start: 12,
41+
length: 8,
42+
},
43+
];
44+
const result = decorateRangesWithShortMentions(ranges, text, [], '@myUser');
45+
expect(result).toEqual([
46+
{
47+
type: 'mention-here',
48+
start: 12,
49+
length: 8,
50+
},
51+
]);
52+
});
53+
54+
test('returns ranges with correct short-mentions', () => {
55+
const text = 'Lorem ipsum @steven.mock';
56+
const ranges: MarkdownRange[] = [
57+
{
58+
type: 'mention-short',
59+
start: 12,
60+
length: 12,
61+
},
62+
];
63+
const availableMentions = ['@johnDoe', '@steven.mock'];
64+
65+
const result = decorateRangesWithShortMentions(ranges, text, availableMentions, '');
66+
expect(result).toEqual([
67+
{
68+
type: 'mention-user',
69+
start: 12,
70+
length: 12,
71+
},
72+
]);
73+
});
74+
75+
test('returns ranges with removed short-mentions when they do not match', () => {
76+
const text = 'Lorem ipsum @steven.mock';
77+
const ranges: MarkdownRange[] = [
78+
{
79+
type: 'bold',
80+
start: 5,
81+
length: 3,
82+
},
83+
{
84+
type: 'mention-short',
85+
start: 12,
86+
length: 12,
87+
},
88+
];
89+
const availableMentions = ['@other.person'];
90+
91+
const result = decorateRangesWithShortMentions(ranges, text, availableMentions, '');
92+
expect(result).toEqual([
93+
{
94+
type: 'bold',
95+
start: 5,
96+
length: 3,
97+
},
98+
]);
99+
});
100+
101+
test('returns ranges with both types of mentions handled', () => {
102+
const text = 'Lorem ipsum @steven.mock @John.current @test';
103+
const ranges: MarkdownRange[] = [
104+
{
105+
type: 'bold',
106+
start: 5,
107+
length: 3,
108+
},
109+
{
110+
type: 'mention-short',
111+
start: 12,
112+
length: 12,
113+
},
114+
{
115+
type: 'mention-short',
116+
start: 25,
117+
length: 13,
118+
},
119+
];
120+
const availableMentions = ['@johnDoe', '@steven.mock', '@John.current'];
121+
const currentUser = '@John.current';
122+
123+
const result = decorateRangesWithShortMentions(ranges, text, availableMentions, currentUser);
124+
expect(result).toEqual([
125+
{
126+
type: 'bold',
127+
start: 5,
128+
length: 3,
129+
},
130+
{
131+
type: 'mention-user',
132+
start: 12,
133+
length: 12,
134+
},
135+
{
136+
type: 'mention-here',
137+
start: 25,
138+
length: 13,
139+
},
140+
]);
141+
});
142+
});

0 commit comments

Comments
 (0)