Skip to content

Commit bd88db1

Browse files
committed
refactor: adjust createTextComposerEmojiMiddleware to the new client typing
1 parent c7a5f25 commit bd88db1

File tree

1 file changed

+70
-72
lines changed

1 file changed

+70
-72
lines changed
Lines changed: 70 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import mergeWith from 'lodash.mergewith';
22
import type {
3+
Middleware,
34
SearchSourceOptions,
45
SearchSourceType,
6+
TextComposerMiddlewareExecutorState,
57
TextComposerMiddlewareOptions,
6-
TextComposerMiddlewareParams,
78
TextComposerSuggestion,
89
} from 'stream-chat';
910
import {
@@ -18,6 +19,9 @@ import type {
1819
EmojiSearchIndexResult,
1920
} from '../../../components/MessageInput';
2021

22+
export type EmojiSuggestion<T extends EmojiSearchIndexResult = EmojiSearchIndexResult> =
23+
TextComposerSuggestion<T>;
24+
2125
class EmojiSearchSource<
2226
T extends TextComposerSuggestion<EmojiSearchIndexResult>,
2327
> extends BaseSearchSource<T> {
@@ -67,6 +71,12 @@ class EmojiSearchSource<
6771

6872
const DEFAULT_OPTIONS: TextComposerMiddlewareOptions = { minChars: 1, trigger: ':' };
6973

74+
export type EmojiMiddleware<T extends EmojiSearchIndexResult = EmojiSearchIndexResult> =
75+
Middleware<
76+
TextComposerMiddlewareExecutorState<EmojiSuggestion<T>>,
77+
'onChange' | 'onSuggestionItemSelect'
78+
>;
79+
7080
/**
7181
* TextComposer middleware for mentions
7282
* Usage:
@@ -84,102 +94,90 @@ const DEFAULT_OPTIONS: TextComposerMiddlewareOptions = { minChars: 1, trigger: '
8494
* }} options
8595
* @returns
8696
*/
87-
export const createTextComposerEmojiMiddleware = <
88-
T extends EmojiSearchIndexResult = EmojiSearchIndexResult,
89-
>(
97+
export const createTextComposerEmojiMiddleware = (
9098
emojiSearchIndex: EmojiSearchIndex,
9199
options?: Partial<TextComposerMiddlewareOptions>,
92-
) => {
100+
): EmojiMiddleware => {
93101
const finalOptions = mergeWith(DEFAULT_OPTIONS, options ?? {});
94102
const emojiSearchSource = new EmojiSearchSource(emojiSearchIndex);
95103
emojiSearchSource.activate();
96104

97105
return {
98106
id: 'stream-io/emoji-middleware',
99-
onChange: async ({ input, nextHandler }: TextComposerMiddlewareParams<T>) => {
100-
const { state } = input;
101-
if (!state.selection) return nextHandler(input);
102-
103-
const triggerWithToken = getTriggerCharWithToken({
104-
acceptTrailingSpaces: false,
105-
text: state.text.slice(0, state.selection.end),
106-
trigger: finalOptions.trigger,
107-
});
108-
109-
const triggerWasRemoved =
110-
!triggerWithToken || triggerWithToken.length < finalOptions.minChars;
111-
112-
if (triggerWasRemoved) {
113-
const hasSuggestionsForTrigger =
114-
input.state.suggestions?.trigger === finalOptions.trigger;
115-
const newInput = { ...input };
116-
if (hasSuggestionsForTrigger && newInput.state.suggestions) {
117-
delete newInput.state.suggestions;
107+
// eslint-disable-next-line sort-keys
108+
handlers: {
109+
onChange: async ({ complete, forward, next, state }) => {
110+
if (!state.selection) return forward();
111+
112+
const triggerWithToken = getTriggerCharWithToken({
113+
acceptTrailingSpaces: false,
114+
text: state.text.slice(0, state.selection.end),
115+
trigger: finalOptions.trigger,
116+
});
117+
118+
const triggerWasRemoved =
119+
!triggerWithToken || triggerWithToken.length < finalOptions.minChars;
120+
121+
if (triggerWasRemoved) {
122+
const hasSuggestionsForTrigger =
123+
state.suggestions?.trigger === finalOptions.trigger;
124+
const newState = { ...state };
125+
if (hasSuggestionsForTrigger && newState.suggestions) {
126+
delete newState.suggestions;
127+
}
128+
return next(newState);
118129
}
119-
return nextHandler(newInput);
120-
}
121130

122-
const newSearchTriggerred =
123-
triggerWithToken && triggerWithToken === finalOptions.trigger;
131+
const newSearchTriggerred =
132+
triggerWithToken && triggerWithToken === finalOptions.trigger;
124133

125-
if (newSearchTriggerred) {
126-
emojiSearchSource.resetStateAndActivate();
127-
}
134+
if (newSearchTriggerred) {
135+
emojiSearchSource.resetStateAndActivate();
136+
}
128137

129-
const textWithReplacedWord = await replaceWordWithEntity({
130-
caretPosition: state.selection.end,
131-
getEntityString: async (word: string) => {
132-
const { items } = await emojiSearchSource.query(word);
138+
const textWithReplacedWord = await replaceWordWithEntity({
139+
caretPosition: state.selection.end,
140+
getEntityString: async (word: string) => {
141+
const { items } = await emojiSearchSource.query(word);
133142

134-
const emoji = items
135-
.filter(Boolean)
136-
.slice(0, 10)
137-
.find(({ emoticons }) => !!emoticons?.includes(word));
143+
const emoji = items
144+
.filter(Boolean)
145+
.slice(0, 10)
146+
.find(({ emoticons }) => !!emoticons?.includes(word));
138147

139-
if (!emoji) return null;
148+
if (!emoji) return null;
140149

141-
const [firstSkin] = emoji.skins ?? [];
150+
const [firstSkin] = emoji.skins ?? [];
142151

143-
return emoji.native ?? firstSkin.native;
144-
},
145-
text: state.text,
146-
});
152+
return emoji.native ?? firstSkin.native;
153+
},
154+
text: state.text,
155+
});
147156

148-
if (textWithReplacedWord !== state.text) {
149-
return {
150-
state: {
157+
if (textWithReplacedWord !== state.text) {
158+
return complete({
151159
...state,
152160
suggestions: undefined, // to prevent the TextComposerMiddlewareExecutor to run the first page query
153161
text: textWithReplacedWord,
154-
},
155-
stop: true, // Stop other middleware from processing '@' character
156-
};
157-
}
162+
});
163+
}
158164

159-
return {
160-
state: {
165+
return complete({
161166
...state,
162167
suggestions: {
163168
query: triggerWithToken.slice(1),
164169
searchSource: emojiSearchSource,
165170
trigger: finalOptions.trigger,
166171
},
167-
},
168-
stop: true, // Stop other middleware from processing '@' character
169-
};
170-
},
171-
onSuggestionItemSelect: ({
172-
input,
173-
nextHandler,
174-
selectedSuggestion,
175-
}: TextComposerMiddlewareParams<T>) => {
176-
const { state } = input;
177-
if (!selectedSuggestion || state.suggestions?.trigger !== finalOptions.trigger)
178-
return nextHandler(input);
179-
180-
emojiSearchSource.resetStateAndActivate();
181-
return Promise.resolve({
182-
state: {
172+
});
173+
},
174+
onSuggestionItemSelect: ({ complete, forward, state }) => {
175+
const { selectedSuggestion } = state.change ?? {};
176+
if (!selectedSuggestion || state.suggestions?.trigger !== finalOptions.trigger)
177+
return forward();
178+
179+
emojiSearchSource.resetStateAndActivate();
180+
return complete({
183181
...state,
184182
...insertItemWithTrigger({
185183
insertText: `${'native' in selectedSuggestion ? selectedSuggestion.native : ''} `,
@@ -188,8 +186,8 @@ export const createTextComposerEmojiMiddleware = <
188186
trigger: finalOptions.trigger,
189187
}),
190188
suggestions: undefined, // Clear suggestions after selection
191-
},
192-
});
189+
});
190+
},
193191
},
194192
};
195193
};

0 commit comments

Comments
 (0)