diff --git a/e2e/jump-to-message.test.ts b/e2e/jump-to-message.test.ts index 014976a49f..ce7994d494 100644 --- a/e2e/jump-to-message.test.ts +++ b/e2e/jump-to-message.test.ts @@ -40,7 +40,7 @@ suiteArray.forEach(([mode, story]) => { }); }); -test.describe('jump to messsage - dataset', () => { +test.describe('jump to message - dataset', () => { test('only the current message set is loaded', async ({ controller, page, user }) => { await controller.openStory( 'jump-to-message--jump-in-regular-message-list', @@ -52,6 +52,6 @@ test.describe('jump to messsage - dataset', () => { page.click(controlsButtonSelector), ]); - await user.sees(MessageList).hasLength(26); + await user.sees(MessageList).hasLength(100 + 1); }); }); diff --git a/package.json b/package.json index e548b17553..7efa9162af 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "peerDependencies": { "react": "^18.0.0 || ^17.0.0 || ^16.8.0", "react-dom": "^18.0.0 || ^17.0.0 || ^16.8.0", - "stream-chat": "^6.7.3" + "stream-chat": "^6.9.0" }, "files": [ "dist", @@ -167,7 +167,7 @@ "rollup-plugin-visualizer": "^4.2.0", "semantic-release": "^19.0.2", "semantic-release-cli": "^5.4.4", - "stream-chat": "^6.7.3", + "stream-chat": "^6.9.0", "style-loader": "^2.0.0", "ts-jest": "^26.5.1", "tslib": "2.3.0", diff --git a/src/components/AutoCompleteTextarea/Textarea.jsx b/src/components/AutoCompleteTextarea/Textarea.jsx index 2374fcadcb..60c40e23ad 100644 --- a/src/components/AutoCompleteTextarea/Textarea.jsx +++ b/src/components/AutoCompleteTextarea/Textarea.jsx @@ -85,7 +85,15 @@ export class ReactTextareaAutocomplete extends React.Component { return this.textareaRef.selectionEnd; }; - _defaultShouldSubmit = (event) => event.key === 'Enter' && !event.shiftKey; + /** + * isComposing prevents double submissions in Korean and other languages. + * starting point for a read: + * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/isComposing + * In the long term, the fix should happen by handling keypress, but changing this has unknown implications. + * @param event React.KeyboardEvent + */ + _defaultShouldSubmit = (event) => + event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing; _handleKeyDown = (event) => { const { shouldSubmit = this._defaultShouldSubmit } = this.props; diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx index d17819eac1..525a3756c8 100644 --- a/src/components/Channel/Channel.tsx +++ b/src/components/Channel/Channel.tsx @@ -190,8 +190,6 @@ export type ChannelProps< VirtualMessage?: ComponentContextValue['VirtualMessage']; }; -const JUMP_MESSAGE_PAGE_SIZE = 25; - const UnMemoizedChannel = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, V extends CustomTrigger = CustomTrigger @@ -526,16 +524,16 @@ const ChannelInner = < return queryResponse.messages.length; }; - const jumpToMessage = async (messageId: string) => { + const jumpToMessage = async (messageId: string, messageLimit = 100) => { dispatch({ loadingMore: true, type: 'setLoadingMore' }); - await channel.state.loadMessageIntoState(messageId); + await channel.state.loadMessageIntoState(messageId, undefined, messageLimit); /** * if the message we are jumping to has less than half of the page size older messages, * we have jumped to the beginning of the channel. */ const indexOfMessage = channel.state.messages.findIndex((message) => message.id === messageId); - const hasMoreMessages = indexOfMessage >= Math.floor(JUMP_MESSAGE_PAGE_SIZE / 2); + const hasMoreMessages = indexOfMessage >= Math.floor(messageLimit / 2); loadMoreFinished(hasMoreMessages, channel.state.messages); dispatch({ diff --git a/src/components/MessageInput/__tests__/MessageInput.test.js b/src/components/MessageInput/__tests__/MessageInput.test.js index 4b79245d8e..ae3745ba43 100644 --- a/src/components/MessageInput/__tests__/MessageInput.test.js +++ b/src/components/MessageInput/__tests__/MessageInput.test.js @@ -31,33 +31,6 @@ import { expect.extend(toHaveNoViolations); -jest.mock('react', () => { - const React = jest.requireActual('react'); - const Suspense = ({ children }) => children; - - const lazy = jest.fn().mockImplementation((fn) => { - const Component = (props) => { - const [C, setC] = React.useState(); - - React.useEffect(() => { - fn().then((v) => { - setC(v); - }); - }, []); - - return C ? : null; - }; - - return Component; - }); - - return { - ...React, - lazy, - Suspense, - }; -}); - jest.mock('../../Channel/utils', () => ({ makeAddNotifications: jest.fn })); let chatClient; diff --git a/src/components/MessageInput/hooks/useAttachments.ts b/src/components/MessageInput/hooks/useAttachments.ts index 97577ce591..f7dae59cda 100644 --- a/src/components/MessageInput/hooks/useAttachments.ts +++ b/src/components/MessageInput/hooks/useAttachments.ts @@ -63,7 +63,13 @@ export const useAttachments = < file.type.startsWith('image/') && !file.type.endsWith('.photoshop') // photoshop files begin with 'image/' ) { - dispatch({ file, id, state: 'uploading', type: 'setImageUpload' }); + dispatch({ + file, + id, + previewUri: URL.createObjectURL?.(file), + state: 'uploading', + type: 'setImageUpload', + }); } else if (file instanceof File && !noFiles) { dispatch({ file, id, state: 'uploading', type: 'setFileUpload' }); } diff --git a/src/components/MessageInput/hooks/useImageUploads.ts b/src/components/MessageInput/hooks/useImageUploads.ts index 3613c6b9f7..7d614296ca 100644 --- a/src/components/MessageInput/hooks/useImageUploads.ts +++ b/src/components/MessageInput/hooks/useImageUploads.ts @@ -97,8 +97,11 @@ export const useImageUploads = < return; } + if (img.previewUri) URL.revokeObjectURL?.(img.previewUri); + dispatch({ id, + previewUri: undefined, state: 'finished', type: 'setImageUpload', url: response.file, @@ -108,32 +111,12 @@ export const useImageUploads = < ); useEffect(() => { - if (FileReader) { - const upload = Object.values(imageUploads).find( - (imageUpload) => - imageUpload.state === 'uploading' && !!imageUpload.file && !imageUpload.previewUri, - ); - if (upload) { - const { file, id } = upload; - // TODO: Possibly use URL.createObjectURL instead. However, then we need - // to release the previews when not used anymore though. - const reader = new FileReader(); - reader.onload = (event) => { - if (typeof event.target?.result !== 'string') return; - dispatch({ - id, - previewUri: event.target.result, - type: 'setImageUpload', - }); - }; - reader.readAsDataURL((file as unknown) as Blob); - uploadImage(id); - return () => { - reader.onload = null; - }; - } - } - return; + const upload = Object.values(imageUploads).find( + (imageUpload) => imageUpload.state === 'uploading' && imageUpload.file, + ); + if (!upload) return; + + uploadImage(upload.id); }, [imageUploads, uploadImage]); return { diff --git a/src/context/ChannelActionContext.tsx b/src/context/ChannelActionContext.tsx index 45d2e9e65d..793bf65050 100644 --- a/src/context/ChannelActionContext.tsx +++ b/src/context/ChannelActionContext.tsx @@ -49,7 +49,7 @@ export type ChannelActionContextValue< message: UpdatedMessage, ) => Promise | void>; jumpToLatestMessage: () => Promise; - jumpToMessage: (messageId: string) => Promise; + jumpToMessage: (messageId: string, limit?: number) => Promise; loadMore: (limit?: number) => Promise; loadMoreNewer: (limit?: number) => Promise; loadMoreThread: () => Promise; diff --git a/src/context/DefaultEmoji.ts b/src/context/DefaultEmoji.ts new file mode 100644 index 0000000000..718b040d4f --- /dev/null +++ b/src/context/DefaultEmoji.ts @@ -0,0 +1,4 @@ +// @ts-expect-error +import NimbleEmoji from 'emoji-mart/dist/components/emoji/nimble-emoji'; + +export { NimbleEmoji as default }; diff --git a/src/context/DefaultEmojiPicker.ts b/src/context/DefaultEmojiPicker.ts new file mode 100644 index 0000000000..fbb22604ff --- /dev/null +++ b/src/context/DefaultEmojiPicker.ts @@ -0,0 +1,4 @@ +// @ts-expect-error +import NimblePicker from 'emoji-mart/dist/components/picker/nimble-picker'; + +export { NimblePicker as default }; diff --git a/src/context/EmojiContext.tsx b/src/context/EmojiContext.tsx index f1cc337068..838e40f4e1 100644 --- a/src/context/EmojiContext.tsx +++ b/src/context/EmojiContext.tsx @@ -49,17 +49,9 @@ export type EmojiContextValue = { EmojiPicker?: React.ComponentType; }; -const DefaultEmoji = React.lazy(async () => { - //@ts-expect-error - const emoji = await import('emoji-mart/dist/components/emoji/nimble-emoji.js'); - return { default: emoji.default }; -}); - -const DefaultEmojiPicker = React.lazy(async () => { - // @ts-expect-error - const emojiPicker = await import('emoji-mart/dist/components/picker/nimble-picker.js'); - return { default: emojiPicker.default }; -}); +const DefaultEmoji = React.lazy(() => import('./DefaultEmoji')); + +const DefaultEmojiPicker = React.lazy(() => import('./DefaultEmojiPicker')); export const EmojiContext = React.createContext(undefined); diff --git a/yarn.lock b/yarn.lock index 221441b40f..1922386737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2925,9 +2925,9 @@ process-es6 "^0.11.2" "@stream-io/stream-chat-css@^2.10.1": - version "2.10.1" - resolved "https://registry.yarnpkg.com/@stream-io/stream-chat-css/-/stream-chat-css-2.10.1.tgz#317b225d2a8a424e056e1d4aff5dc0f7f9b575ed" - integrity sha512-8ykbkvxOWHbagRMCacXBzta3FbB0Vs+1wGRKy9IP2Md4Rm88Id9Cti6ikYi5ltmjnpyvsWSe+G/G1CvTmFpnqA== + version "2.11.0" + resolved "https://registry.yarnpkg.com/@stream-io/stream-chat-css/-/stream-chat-css-2.11.0.tgz#fdf0a3fd557f09ca02a344c7a6b70027df37068c" + integrity sha512-lsPEWpsB3ygNSO0PrTULgcCyxrCH5NWg0WuCbWP/i7fF5Slqswrs+yF6uEx6tBMW+pBZmOBPvbhHTG+CK2iczg== "@stream-io/transliterate@^1.5.5": version "1.5.5" @@ -3207,7 +3207,7 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== -"@types/mdast@^3.0.0", "@types/mdast@^3.0.3", "@types/mdast@^3.0.10": +"@types/mdast@^3.0.0", "@types/mdast@^3.0.10", "@types/mdast@^3.0.3": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== @@ -16050,10 +16050,10 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-chat@^6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-6.7.3.tgz#5d3a0f1678c5bcac6241e15882230bf71b56d06d" - integrity sha512-O2gBocLG4rgAMOHq4PlXDkexfgkXFp1ZEGF+W3lllFXBC2/e52KbbojQNMhyoQPv82n2JQCGpkychUP4jdpn9Q== +stream-chat@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-6.9.0.tgz#cc5c8e68849b36a891178b6b39cd150fc8a69ddf" + integrity sha512-GERt6ohbMucs1mOFY6xoU3p9gl0oEQieod09hSiaRnHG++LP//SQafYlSyE9v2n368VQcc6OSiP5tAinBWafJw== dependencies: "@babel/runtime" "^7.16.3" "@types/jsonwebtoken" "^8.5.6"