From 61c4eecf5c03ab36109a94b7afa5f678e99fcc8b Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Thu, 4 Aug 2022 14:09:47 +0200 Subject: [PATCH 1/6] fix: include mdast-util-find-and-replace into our CJS bundle (#1702) Our recent upgrade of `mdast-util-find-and-replace` in #1698 broke our CommonJS bundle. The `mdast-util-find-and-replace` package is now bundled within our CJS bundle. This will eliminate the conflict between ESM and CJS modules in NodeJS. As part of this change, a very naive smoke test is introduced aiming for earlier detection of such incompatibilities. --- .github/workflows/ci.yml | 3 +++ .github/workflows/release.yml | 2 ++ package.json | 1 + rollup.config.js | 16 +++++++++------- scripts/validate-cjs-bundle.cjs | 3 +++ 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 scripts/validate-cjs-bundle.cjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9c777851f..ba433d0358 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,3 +93,6 @@ jobs: yarn lint yarn coverage yarn validate-translations + + - name: 🧪 Validate CommonJS bundle with ${{ matrix.node }} + run: yarn validate-cjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7fdd47961..2a8b26f153 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,8 @@ jobs: node-version: 'lts/*' - name: Install dependencies run: yarn install --frozen-lockfile + - name: Validate CommonJS bundle + run: yarn validate-cjs - name: Release env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/package.json b/package.json index d654419108..e548b17553 100644 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ "test": "jest", "types": "tsc --strict", "validate-translations": "node scripts/validate-translations.js", + "validate-cjs": "node scripts/validate-cjs-bundle.cjs", "semantic-release": "semantic-release", "browse-examples": "ladle serve", "e2e": "playwright test", diff --git a/rollup.config.js b/rollup.config.js index 94ad74b9c7..7bf9594e9f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -33,6 +33,7 @@ const externalDependencies = [ '@braintree/sanitize-url', '@fortawesome/free-regular-svg-icons', '@fortawesome/react-fontawesome', + '@juggle/resize-observer', '@stream-io/transliterate', 'custom-event', /dayjs/, @@ -45,13 +46,14 @@ const externalDependencies = [ 'lodash.isequal', 'lodash.throttle', 'lodash.uniqby', - 'mdast-util-find-and-replace', 'mml-react', + 'nanoid', 'pretty-bytes', 'prop-types', 'react-fast-compare', /react-file-utils/, 'react-images', + 'react-image-gallery', 'react-is', /react-markdown/, 'react-player', @@ -61,7 +63,7 @@ const externalDependencies = [ /uuid/, ]; -const basePlugins = [ +const basePlugins = ({ useBrowserResolve = false }) => [ replace({ preventAssignment: true, 'process.env.NODE_ENV': JSON.stringify('production'), @@ -69,6 +71,9 @@ const basePlugins = [ // Remove peer-dependencies from final bundle external(), image(), + resolve({ + browser: useBrowserResolve, + }), typescript(), babel({ babelHelpers: 'runtime', @@ -107,7 +112,7 @@ const normalBundle = { sourcemap: true, }, ], - plugins: [...basePlugins], + plugins: [...basePlugins({ useBrowserResolve: false })], }; const fullBrowserBundle = ({ min } = { min: false }) => ({ @@ -126,16 +131,13 @@ const fullBrowserBundle = ({ min } = { min: false }) => ({ }, ], plugins: [ - ...basePlugins, + ...basePlugins({ useBrowserResolve: true }), { load: (id) => (id.match(/.s?css$/) ? '' : null), name: 'ignore-css-and-scss', resolveId: (importee) => (importee.match(/.s?css$/) ? importee : null), }, builtins(), - resolve({ - browser: true, - }), globals({ buffer: false, dirname: false, diff --git a/scripts/validate-cjs-bundle.cjs b/scripts/validate-cjs-bundle.cjs new file mode 100644 index 0000000000..d17162a995 --- /dev/null +++ b/scripts/validate-cjs-bundle.cjs @@ -0,0 +1,3 @@ +// As the community transitions to ESM, we can easily break our CJS bundle. +// This smoke test can help to detect this early. +require('../dist/index.cjs.js'); From c8a490ebc53da03c2b0f064de88c0cb634ed2a70 Mon Sep 17 00:00:00 2001 From: Anton Arnautov <43254280+arnautov-anton@users.noreply.github.com> Date: Wed, 17 Aug 2022 01:36:06 +0200 Subject: [PATCH 2/6] fix: replace FileReader with URL.createObjectURL (#1701) --- .../MessageInput/hooks/useAttachments.ts | 8 ++++- .../MessageInput/hooks/useImageUploads.ts | 35 +++++-------------- 2 files changed, 16 insertions(+), 27 deletions(-) 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 { From 8c720f41e349f753a126ad5e062c1475e3893771 Mon Sep 17 00:00:00 2001 From: Petyo Ivanov Date: Fri, 19 Aug 2022 17:18:47 +0300 Subject: [PATCH 3/6] feat: increase and support overriding jump to message limit (#1718) --- e2e/jump-to-message.test.ts | 4 ++-- package.json | 4 ++-- src/components/Channel/Channel.tsx | 8 +++----- src/context/ChannelActionContext.tsx | 2 +- yarn.lock | 10 +++++----- 5 files changed, 13 insertions(+), 15 deletions(-) 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/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/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/yarn.lock b/yarn.lock index 221441b40f..a78b34c585 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" From 5d781d896cb9153bcf3554d04714215c0bbf5c12 Mon Sep 17 00:00:00 2001 From: Petyo Ivanov Date: Mon, 22 Aug 2022 16:11:19 +0300 Subject: [PATCH 4/6] fix: prevent double submissions in korean (#1720) --- src/components/AutoCompleteTextarea/Textarea.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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; From 515298dc0987c9956d8e567906e28a0d21a15553 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 07:26:36 +0200 Subject: [PATCH 5/6] chore(deps): bump @stream-io/stream-chat-css from 2.10.1 to 2.11.0 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a78b34c585..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" From c90cf4bfc6b6aa74233fd041200e8180a70604e4 Mon Sep 17 00:00:00 2001 From: Anton Arnautov <43254280+arnautov-anton@users.noreply.github.com> Date: Fri, 26 Aug 2022 11:30:08 +0200 Subject: [PATCH 6/6] fix(Vite): add emoji-mart (emoji, picker) re-export (#1724) * fix(Vite): add emoji-mart (emoji, picker) re-export * test(MessageInput): remove unused React.Suspense mock --- .../__tests__/MessageInput.test.js | 27 ------------------- src/context/DefaultEmoji.ts | 4 +++ src/context/DefaultEmojiPicker.ts | 4 +++ src/context/EmojiContext.tsx | 14 +++------- 4 files changed, 11 insertions(+), 38 deletions(-) create mode 100644 src/context/DefaultEmoji.ts create mode 100644 src/context/DefaultEmojiPicker.ts 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/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);