Skip to content

Commit 660f2a0

Browse files
authored
Merge pull request #670 from GetStream/textarea-paste-limit
textarea-paste-limit
2 parents 37b5e5a + 5a4cbe2 commit 660f2a0

File tree

6 files changed

+65
-41
lines changed

6 files changed

+65
-41
lines changed

src/components/MessageInput/MessageInputFlat.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ MessageInputFlat.propTypes = {
122122
/** enable/disable firing the typing event */
123123
publishTypingEvent: PropTypes.bool,
124124
/**
125-
* Any additional attrubutes that you may want to add for underlying HTML textarea element.
125+
* Any additional attributes that you may want to add for underlying HTML textarea element.
126126
*/
127-
additionalTextareaProps: PropTypes.object,
127+
additionalTextareaProps: /** @type {PropTypes.Validator<React.TextareaHTMLAttributes<import('types').AnyType>>} */ (PropTypes.object),
128128
/**
129129
* Override the default triggers of the ChatAutoComplete component
130130
*/

src/components/MessageInput/MessageInputLarge.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ MessageInputLarge.propTypes = {
167167
/** enable/disable firing the typing event */
168168
publishTypingEvent: PropTypes.bool,
169169
/**
170-
* Any additional attrubutes that you may want to add for underlying HTML textarea element.
170+
* Any additional attributes that you may want to add for underlying HTML textarea element.
171171
*/
172-
additionalTextareaProps: PropTypes.object,
172+
additionalTextareaProps: /** @type {PropTypes.Validator<React.TextareaHTMLAttributes<import('types').AnyType>>} */ (PropTypes.object),
173173
/**
174174
* Override the default triggers of the ChatAutoComplete component
175175
*/

src/components/MessageInput/MessageInputSimple.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ MessageInputSimple.propTypes = {
7878
/** enable/disable firing the typing event */
7979
publishTypingEvent: PropTypes.bool,
8080
/**
81-
* Any additional attrubutes that you may want to add for underlying HTML textarea element.
81+
* Any additional attributes that you may want to add for underlying HTML textarea element.
8282
*/
83-
additionalTextareaProps: PropTypes.object,
83+
additionalTextareaProps: /** @type {PropTypes.Validator<React.TextareaHTMLAttributes<import('types').AnyType>>} */ (PropTypes.object),
8484
/**
8585
* Override the default triggers of the ChatAutoComplete component
8686
*/

src/components/MessageInput/MessageInputSmall.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ MessageInputSmall.propTypes = {
123123
/** enable/disable firing the typing event */
124124
publishTypingEvent: PropTypes.bool,
125125
/**
126-
* Any additional attrubutes that you may want to add for underlying HTML textarea element.
126+
* Any additional attributes that you may want to add for underlying HTML textarea element.
127127
*/
128-
additionalTextareaProps: PropTypes.object,
128+
additionalTextareaProps: /** @type {PropTypes.Validator<React.TextareaHTMLAttributes<import('types').AnyType>>} */ (PropTypes.object),
129129
/**
130130
* Override the default triggers of the ChatAutoComplete component
131131
*/

src/components/MessageInput/hooks/messageInput.js

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// @ts-check
21
import {
32
useReducer,
43
useEffect,
@@ -62,7 +61,7 @@ function initState(message) {
6261
};
6362
}
6463

65-
// if message prop is defined, get imageuploads, fileuploads, text, etc. from it
64+
// if message prop is defined, get image uploads, file uploads, text, etc. from it
6665
const imageUploads =
6766
message.attachments
6867
?.filter(({ type }) => type === 'image')
@@ -212,24 +211,29 @@ function messageInputReducer(state, action) {
212211
*/
213212
export default function useMessageInput(props) {
214213
const {
214+
additionalTextareaProps,
215+
clearEditingState,
215216
doImageUploadRequest,
216217
doFileUploadRequest,
218+
errorHandler,
217219
focus,
218220
message,
219-
clearEditingState,
221+
noFiles,
220222
overrideSubmitHandler,
221223
parent,
222-
noFiles,
223-
errorHandler,
224224
publishTypingEvent,
225225
} = props;
226226

227+
const {
228+
channel,
229+
editMessage,
230+
maxNumberOfFiles,
231+
multipleUploads,
232+
sendMessage,
233+
} = useContext(ChannelContext);
234+
227235
const [state, dispatch] = useReducer(messageInputReducer, message, initState);
228-
const textareaRef = useRef(
229-
/** @type {HTMLTextAreaElement | undefined} */ (undefined),
230-
);
231-
const emojiPickerRef = useRef(/** @type {HTMLDivElement | null} */ (null));
232-
const channelContext = useContext(ChannelContext);
236+
233237
const {
234238
text,
235239
imageOrder,
@@ -240,10 +244,13 @@ export default function useMessageInput(props) {
240244
numberOfUploads,
241245
mentioned_users,
242246
} = state;
243-
const { channel, editMessage, sendMessage } = channelContext;
244247

245-
// Focus
248+
const textareaRef = useRef(
249+
/** @type {HTMLTextAreaElement | undefined} */ (undefined),
250+
);
251+
const emojiPickerRef = useRef(/** @type {HTMLDivElement | null} */ (null));
246252

253+
// Focus
247254
useEffect(() => {
248255
if (focus && textareaRef.current) {
249256
textareaRef.current.focus();
@@ -252,28 +259,45 @@ export default function useMessageInput(props) {
252259

253260
// Text + cursor position
254261
const newCursorPosition = useRef(/** @type {number | null} */ (null));
262+
255263
const insertText = useCallback(
256264
(textToInsert) => {
265+
const { maxLength } = additionalTextareaProps;
266+
257267
if (!textareaRef.current) {
258268
dispatch({
259269
type: 'setText',
260-
getNewText: (t) => t + textToInsert,
270+
getNewText: (t) => {
271+
const updatedText = t + textToInsert;
272+
if (updatedText.length > maxLength) {
273+
return updatedText.slice(0, maxLength);
274+
}
275+
return updatedText;
276+
},
261277
});
262278
return;
263279
}
264280

265-
const textareaElement = textareaRef.current;
266-
const { selectionStart, selectionEnd } = textareaElement;
281+
const { selectionStart, selectionEnd } = textareaRef.current;
267282
newCursorPosition.current = selectionStart + textToInsert.length;
283+
268284
dispatch({
269285
type: 'setText',
270-
getNewText: (prevText) =>
271-
prevText.slice(0, selectionStart) +
272-
textToInsert +
273-
prevText.slice(selectionEnd),
286+
getNewText: (prevText) => {
287+
const updatedText =
288+
prevText.slice(0, selectionStart) +
289+
textToInsert +
290+
prevText.slice(selectionEnd);
291+
292+
if (updatedText.length > maxLength) {
293+
return updatedText.slice(0, maxLength);
294+
}
295+
296+
return updatedText;
297+
},
274298
});
275299
},
276-
[textareaRef, newCursorPosition],
300+
[additionalTextareaProps, newCursorPosition, textareaRef],
277301
);
278302

279303
useEffect(() => {
@@ -524,7 +548,7 @@ export default function useMessageInput(props) {
524548
dispatch({ type: 'setFileUpload', id, state: 'failed' });
525549
}
526550
if (!alreadyRemoved && errorHandler) {
527-
// TODO: verify if the paramaters passed to the error handler actually make sense
551+
// TODO: verify if the parameters passed to the error handler actually make sense
528552
errorHandler(e, 'upload-file', file);
529553
}
530554
return;
@@ -579,7 +603,7 @@ export default function useMessageInput(props) {
579603
dispatch({ type: 'setImageUpload', id, state: 'failed' });
580604
}
581605
if (!alreadyRemoved && errorHandler) {
582-
// TODO: verify if the paramaters passed to the error handler actually make sense
606+
// TODO: verify if the parameters passed to the error handler actually make sense
583607
errorHandler(e, 'upload-image', {
584608
id,
585609
file,
@@ -639,12 +663,12 @@ export default function useMessageInput(props) {
639663
// Number of files that the user can still add. Should never be more than the amount allowed by the API.
640664
// If multipleUploads is false, we only want to allow a single upload.
641665
const maxFilesAllowed = useMemo(() => {
642-
if (!channelContext.multipleUploads) return 1;
643-
if (channelContext.maxNumberOfFiles === undefined) {
666+
if (!multipleUploads) return 1;
667+
if (maxNumberOfFiles === undefined) {
644668
return apiMaxNumberOfFiles;
645669
}
646-
return channelContext.maxNumberOfFiles;
647-
}, [channelContext.maxNumberOfFiles, channelContext.multipleUploads]);
670+
return maxNumberOfFiles;
671+
}, [maxNumberOfFiles, multipleUploads]);
648672

649673
const maxFilesLeft = maxFilesAllowed - numberOfUploads;
650674

@@ -673,9 +697,7 @@ export default function useMessageInput(props) {
673697
(async (event) => {
674698
// TODO: Move this handler to package with ImageDropzone
675699
const { items } = event.clipboardData;
676-
if (!dataTransferItemsHaveFiles(items)) {
677-
return;
678-
}
700+
if (!dataTransferItemsHaveFiles(items)) return;
679701

680702
event.preventDefault();
681703
// Get a promise for the plain text in case no files are
@@ -686,6 +708,7 @@ export default function useMessageInput(props) {
686708
const plainTextItem = [...items].find(
687709
({ kind, type }) => kind === 'string' && type === 'text/plain',
688710
);
711+
689712
if (plainTextItem) {
690713
plainTextPromise = new Promise((resolve) => {
691714
plainTextItem.getAsString((s) => {
@@ -699,14 +722,15 @@ export default function useMessageInput(props) {
699722
uploadNewFiles(fileLikes);
700723
return;
701724
}
725+
702726
// fallback to regular text paste
703727
if (plainTextPromise) {
704-
const s = await plainTextPromise;
705-
insertText(s);
728+
const pastedText = await plainTextPromise;
729+
insertText(pastedText);
706730
}
707731
})(e);
708732
},
709-
[uploadNewFiles, insertText],
733+
[insertText, uploadNewFiles],
710734
);
711735

712736
const isUploadEnabled = channel?.getConfig?.()?.uploads !== false;

types/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ export interface MessageInputProps {
709709
* }}
710710
* />
711711
*/
712-
additionalTextareaProps?: object;
712+
additionalTextareaProps?: React.TextareaHTMLAttributes;
713713
/** Message object. If defined, the message passed will be edited, instead of a new message being created */
714714
message?: Client.MessageResponse;
715715
/** Callback to clear editing state in parent component */

0 commit comments

Comments
 (0)