From 39831ccfe5dd577706c454e44f9ae7bc750f57eb Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Jun 2025 08:44:26 +0200 Subject: [PATCH 1/5] feat: allow to send thread reply to channel --- src/components/Channel/Channel.tsx | 7 + ...eIsThreadReplyInChannelButtonIndicator.tsx | 68 ++++++ src/components/Message/MessageSimple.tsx | 14 +- .../Message/__tests__/MessageSimple.test.js | 80 +++++++ .../__tests__/MessageActions.test.js | 4 +- .../MessageInput/MessageInputFlat.tsx | 3 + .../MessageInput/SendToChannelCheckbox.tsx | 35 +++ .../__tests__/EditMessageForm.test.js | 9 + .../__tests__/MessageInput.test.js | 11 + .../__tests__/ThreadMessageInput.test.js | 200 ++++++++++++++++++ src/components/Thread/LegacyThreadContext.ts | 8 + src/components/Thread/Thread.tsx | 11 +- src/components/Thread/index.ts | 1 + src/context/ComponentContext.tsx | 4 + src/i18n/de.json | 3 + src/i18n/en.json | 3 + src/i18n/es.json | 3 + src/i18n/fr.json | 3 + src/i18n/hi.json | 3 + src/i18n/it.json | 3 + src/i18n/ja.json | 3 + src/i18n/ko.json | 3 + src/i18n/nl.json | 3 + src/i18n/pt.json | 3 + src/i18n/ru.json | 3 + src/i18n/tr.json | 3 + 26 files changed, 477 insertions(+), 14 deletions(-) create mode 100644 src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx create mode 100644 src/components/MessageInput/SendToChannelCheckbox.tsx create mode 100644 src/components/MessageInput/__tests__/ThreadMessageInput.test.js create mode 100644 src/components/Thread/LegacyThreadContext.ts diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx index fb0fe75596..5499be401a 100644 --- a/src/components/Channel/Channel.tsx +++ b/src/components/Channel/Channel.tsx @@ -118,6 +118,7 @@ type ChannelPropsForwardedToComponentContext = Pick< | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' + | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' @@ -141,6 +142,7 @@ type ChannelPropsForwardedToComponentContext = Pick< | 'ReactionsList' | 'ReactionsListModal' | 'SendButton' + | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' @@ -1206,6 +1208,8 @@ const ChannelInner = ( MessageBlocked: props.MessageBlocked, MessageBouncePrompt: props.MessageBouncePrompt, MessageDeleted: props.MessageDeleted, + MessageIsThreadReplyInChannelButtonIndicator: + props.MessageIsThreadReplyInChannelButtonIndicator, MessageListNotifications: props.MessageListNotifications, MessageNotification: props.MessageNotification, MessageOptions: props.MessageOptions, @@ -1228,6 +1232,7 @@ const ChannelInner = ( ReactionsList: props.ReactionsList, ReactionsListModal: props.ReactionsListModal, SendButton: props.SendButton, + SendToChannelCheckbox: props.SendToChannelCheckbox, StartRecordingAudioButton: props.StartRecordingAudioButton, StopAIGenerationButton: props.StopAIGenerationButton, StreamedMessageText: props.StreamedMessageText, @@ -1269,6 +1274,7 @@ const ChannelInner = ( props.MessageBlocked, props.MessageBouncePrompt, props.MessageDeleted, + props.MessageIsThreadReplyInChannelButtonIndicator, props.MessageListNotifications, props.MessageNotification, props.MessageOptions, @@ -1291,6 +1297,7 @@ const ChannelInner = ( props.ReactionsList, props.ReactionsListModal, props.SendButton, + props.SendToChannelCheckbox, props.StartRecordingAudioButton, props.StopAIGenerationButton, props.StreamedMessageText, diff --git a/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx b/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx new file mode 100644 index 0000000000..bf526a6428 --- /dev/null +++ b/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx @@ -0,0 +1,68 @@ +import React, { useEffect, useRef } from 'react'; +import type { LocalMessage } from 'stream-chat'; +import { formatMessage } from 'stream-chat'; +import { + useChannelActionContext, + useChannelStateContext, + useMessageContext, + useTranslationContext, +} from '../../context'; + +export const MessageIsThreadReplyInChannelButtonIndicator = () => { + const { t } = useTranslationContext(); + const { channel } = useChannelStateContext(); + const { openThread } = useChannelActionContext(); + const { message } = useMessageContext(); + const parentMessageRef = useRef(undefined); + + const querySearchParent = () => + channel + .getClient() + .search({ cid: channel.cid }, { id: message.parent_id }) + .then(({ results }) => { + if (!results.length) return; + parentMessageRef.current = formatMessage(results[0].message); + }); + + useEffect(() => { + if ( + parentMessageRef.current || + parentMessageRef.current === null || + !message.parent_id + ) + return; + const localMessage = channel.state.findMessage(message.parent_id); + if (localMessage) { + parentMessageRef.current = localMessage; + return; + } + }, [channel, message]); + + if (!message.parent_id) return null; + + return ( +
+ +
+ ); +}; diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index 9b4c23b875..8831626e4e 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -10,6 +10,9 @@ import { MessageRepliesCountButton as DefaultMessageRepliesCountButton } from '. import { MessageStatus as DefaultMessageStatus } from './MessageStatus'; import { MessageText } from './MessageText'; import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp'; +import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText'; +import { isDateSeparatorMessage } from '../MessageList'; +import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator'; import { areMessageUIPropsEqual, isMessageBlocked, @@ -35,9 +38,6 @@ import { MessageEditedTimestamp } from './MessageEditedTimestamp'; import type { MessageUIComponentProps } from './types'; -import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText'; -import { isDateSeparatorMessage } from '../MessageList'; - type MessageSimpleWithContextProps = MessageContextValue; const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { @@ -72,8 +72,9 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { // major release and use the new default instead MessageActions = MessageOptions, MessageBlocked = DefaultMessageBlocked, - MessageDeleted = DefaultMessageDeleted, MessageBouncePrompt = DefaultMessageBouncePrompt, + MessageDeleted = DefaultMessageDeleted, + MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, @@ -102,6 +103,8 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { const showMetadata = !groupedByUser || endOfGroup; const showReplyCountButton = !threadList && !!message.reply_count; + const showIsReplyInChannel = + !threadList && message.show_in_channel && message.parent_id; const allowRetry = message.status === 'failed' && message.error?.status !== 403; const isBounced = isMessageBounced(message); const isEdited = isMessageEdited(message) && !isAIGenerated; @@ -131,7 +134,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { 'str-chat__message--with-reactions': hasReactions, 'str-chat__message-send-can-be-retried': message?.status === 'failed' && message?.error?.status !== 403, - 'str-chat__message-with-thread-link': showReplyCountButton, + 'str-chat__message-with-thread-link': showReplyCountButton || showIsReplyInChannel, 'str-chat__virtual-message__wrapper--end': endOfGroup, 'str-chat__virtual-message__wrapper--first': firstOfGroup, 'str-chat__virtual-message__wrapper--group': groupedByUser, @@ -205,6 +208,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { reply_count={message.reply_count} /> )} + {showIsReplyInChannel && } {showMetadata && (
diff --git a/src/components/Message/__tests__/MessageSimple.test.js b/src/components/Message/__tests__/MessageSimple.test.js index c158ad202e..02e12691c4 100644 --- a/src/components/Message/__tests__/MessageSimple.test.js +++ b/src/components/Message/__tests__/MessageSimple.test.js @@ -216,6 +216,23 @@ describe('', () => { expect(results).toHaveNoViolations(); }); + it('should render message with custom message-is-reply indicator', async () => { + const message = generateAliceMessage({ parent_id: 'x', show_in_channel: true }); + const CustomMessageIsThreadReplyInChannelButtonIndicator = () => ( +
Is Reply
+ ); + const { container, getByTestId } = await renderMessageSimple({ + components: { + MessageIsThreadReplyInChannelButtonIndicator: + CustomMessageIsThreadReplyInChannelButtonIndicator, + }, + message, + }); + expect(getByTestId('custom-message-is-reply')).toBeInTheDocument(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + it('should render message with custom options component when one is given', async () => { const message = generateAliceMessage({ text: '' }); const CustomOptions = () =>
Options
; @@ -613,6 +630,69 @@ describe('', () => { expect(results).toHaveNoViolations(); }); + it('should display is-message-reply button', async () => { + const message = generateAliceMessage({ + parent_id: 'x', + show_in_channel: true, + }); + const { container, getByTestId } = await renderMessageSimple({ message }); + expect(getByTestId('message-is-thread-reply-button')).toBeInTheDocument(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should open thread when is-message-reply button is clicked', async () => { + const parentMessage = generateMessage({ id: 'x' }); + const message = generateAliceMessage({ + parent_id: parentMessage.id, + show_in_channel: true, + }); + channel.state.messageSets[0].messages.unshift(parentMessage); + const { container, getByTestId } = await renderMessageSimple({ + message, + }); + expect(openThreadMock).not.toHaveBeenCalled(); + fireEvent.click(getByTestId('message-is-thread-reply-button')); + expect(openThreadMock).toHaveBeenCalledWith(expect.any(Object)); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should not open thread when is-message-reply button is clicked and parent message is not found', async () => { + const parentMessage = generateMessage({ id: 'x' }); + const message = generateAliceMessage({ + parent_id: parentMessage.id, + show_in_channel: true, + }); + const { container, getByTestId } = await renderMessageSimple({ + message, + }); + expect(openThreadMock).not.toHaveBeenCalled(); + fireEvent.click(getByTestId('message-is-thread-reply-button')); + expect(openThreadMock).not.toHaveBeenCalled(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should query the parent if not found in local state', async () => { + const parentMessage = generateMessage({ id: 'x' }); + const message = generateAliceMessage({ + parent_id: parentMessage.id, + show_in_channel: true, + }); + const searchSpy = jest.spyOn(client, 'search'); + const { container, getByTestId } = await renderMessageSimple({ + message, + }); + fireEvent.click(getByTestId('message-is-thread-reply-button')); + expect(searchSpy).toHaveBeenCalledWith( + { cid: channel.cid }, + { id: parentMessage.id }, + ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + it('should open thread when reply count button is clicked', async () => { const message = generateAliceMessage({ reply_count: 1, diff --git a/src/components/MessageActions/__tests__/MessageActions.test.js b/src/components/MessageActions/__tests__/MessageActions.test.js index ebb91a820a..312faa45be 100644 --- a/src/components/MessageActions/__tests__/MessageActions.test.js +++ b/src/components/MessageActions/__tests__/MessageActions.test.js @@ -131,7 +131,9 @@ describe(' component', () => { expect(MessageActionsBoxMock).not.toHaveBeenCalled(); const dialogOverlay = screen.getByTestId(dialogOverlayTestId); expect(dialogOverlay.children).toHaveLength(0); - await toggleOpenMessageActions(); + await act(async () => { + await toggleOpenMessageActions(); + }); expect(MessageActionsBoxMock).toHaveBeenLastCalledWith( expect.objectContaining({ open: true }), undefined, diff --git a/src/components/MessageInput/MessageInputFlat.tsx b/src/components/MessageInput/MessageInputFlat.tsx index 21f9bf1541..e6536b34d0 100644 --- a/src/components/MessageInput/MessageInputFlat.tsx +++ b/src/components/MessageInput/MessageInputFlat.tsx @@ -19,6 +19,7 @@ import { QuotedMessagePreviewHeader, } from './QuotedMessagePreview'; import { LinkPreviewList as DefaultLinkPreviewList } from './LinkPreviewList'; +import { SendToChannelCheckbox as DefaultSendToChannelCheckbox } from './SendToChannelCheckbox'; import { TextareaComposer as DefaultTextareaComposer } from '../TextareaComposer'; import { AIStates, useAIState } from '../AIStateIndicator'; import { RecordingAttachmentType } from '../MediaRecorder/classes'; @@ -51,6 +52,7 @@ export const MessageInputFlat = () => { QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, + SendToChannelCheckbox = DefaultSendToChannelCheckbox, StartRecordingAudioButton = DefaultStartRecordingAudioButton, StopAIGenerationButton: StopAIGenerationButtonOverride, TextareaComposer = DefaultTextareaComposer, @@ -146,6 +148,7 @@ export const MessageInputFlat = () => { ) )}
+ ); }; diff --git a/src/components/MessageInput/SendToChannelCheckbox.tsx b/src/components/MessageInput/SendToChannelCheckbox.tsx new file mode 100644 index 0000000000..46b06f8bd0 --- /dev/null +++ b/src/components/MessageInput/SendToChannelCheckbox.tsx @@ -0,0 +1,35 @@ +import { useMessageComposer } from './hooks'; +import React from 'react'; +import type { MessageComposerState } from 'stream-chat'; +import { useStateStore } from '../../store'; +import { useTranslationContext } from '../../context'; + +const stateSelector = (state: MessageComposerState) => ({ + showReplyInChannel: state.showReplyInChannel, +}); + +export const SendToChannelCheckbox = () => { + const { t } = useTranslationContext(); + const messageComposer = useMessageComposer(); + const { showReplyInChannel } = useStateStore(messageComposer.state, stateSelector); + + if (messageComposer.editedMessage || !messageComposer.threadId) return null; + + return ( +
+
+ + +
+
+ ); +}; diff --git a/src/components/MessageInput/__tests__/EditMessageForm.test.js b/src/components/MessageInput/__tests__/EditMessageForm.test.js index 30ac900b61..dc0c408ead 100644 --- a/src/components/MessageInput/__tests__/EditMessageForm.test.js +++ b/src/components/MessageInput/__tests__/EditMessageForm.test.js @@ -1470,4 +1470,13 @@ describe(`EditMessageForm`, () => { jest.useRealTimers(); }); }); + + it('should not render the SendToChannelCheckbox content', async () => { + const { customChannel, customClient } = await setup(); + await renderComponent({ + customChannel, + customClient, + }); + expect(screen.queryByTestId('send-to-channel-checkbox')).not.toBeInTheDocument(); + }); }); diff --git a/src/components/MessageInput/__tests__/MessageInput.test.js b/src/components/MessageInput/__tests__/MessageInput.test.js index 241139a273..0ce72535ec 100644 --- a/src/components/MessageInput/__tests__/MessageInput.test.js +++ b/src/components/MessageInput/__tests__/MessageInput.test.js @@ -1343,4 +1343,15 @@ describe(`MessageInputFlat`, () => { jest.useRealTimers(); }); }); + + describe('SendToChannelCheckbox', () => { + it('does not render in the channel context', async () => { + const { customChannel, customClient } = await setup(); + await renderComponent({ + customChannel, + customClient, + }); + expect(screen.queryByTestId('send-to-channel-checkbox')).not.toBeInTheDocument(); + }); + }); }); diff --git a/src/components/MessageInput/__tests__/ThreadMessageInput.test.js b/src/components/MessageInput/__tests__/ThreadMessageInput.test.js new file mode 100644 index 0000000000..1ef5269d1f --- /dev/null +++ b/src/components/MessageInput/__tests__/ThreadMessageInput.test.js @@ -0,0 +1,200 @@ +import '@testing-library/jest-dom'; +import { + generateChannel, + generateMember, + generateMessage, + generateUser, + initClientWithChannels, +} from '../../../mock-builders'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { ChatProvider, MessageProvider, useChannelActionContext } from '../../../context'; +import { Channel } from '../../Channel'; +import { MessageActionsBox } from '../../MessageActions'; +import React, { useEffect, useRef } from 'react'; +import { SearchController } from 'stream-chat'; +import { MessageInput } from '../MessageInput'; +import { LegacyThreadContext } from '../../Thread/LegacyThreadContext'; + +const sendMessageMock = jest.fn(); +const fileUploadUrl = 'http://www.getstream.io'; +const cid = 'messaging:general'; +const userId = 'userId'; +const username = 'username'; +const mentionId = 'mention-id'; +const mentionName = 'mention-name'; +const user = generateUser({ id: userId, name: username }); +const mentionUser = generateUser({ + id: mentionId, + name: mentionName, +}); +const mainListMessage = generateMessage({ cid, user }); +const threadMessage = generateMessage({ + parent_id: mainListMessage.id, + type: 'reply', + user, +}); + +const mockedChannelData = generateChannel({ + channel: { + id: 'general', + own_capabilities: ['send-poll', 'upload-file'], + type: 'messaging', + }, + members: [generateMember({ user }), generateMember({ user: mentionUser })], + messages: [mainListMessage], + thread: [threadMessage], +}); + +const defaultChatContext = { + channelsQueryState: { queryInProgress: 'uninitialized' }, + getAppSettings: jest.fn(), + latestMessageDatesByChannels: {}, + mutes: [], + searchController: new SearchController(), +}; + +const defaultMessageContextValue = { + getMessageActions: () => ['delete', 'edit', 'quote'], + handleDelete: () => {}, + handleFlag: () => {}, + handleMute: () => {}, + handlePin: () => {}, + isMyMessage: () => true, + message: mainListMessage, + setEditingState: () => {}, +}; + +const setup = async ({ channelData } = {}) => { + const { + channels: [customChannel], + client: customClient, + } = await initClientWithChannels({ + channelsData: [channelData ?? mockedChannelData], + customUser: user, + }); + const sendImageSpy = jest.spyOn(customChannel, 'sendImage').mockResolvedValueOnce({ + file: fileUploadUrl, + }); + const sendFileSpy = jest.spyOn(customChannel, 'sendFile').mockResolvedValueOnce({ + file: fileUploadUrl, + }); + const getDraftSpy = jest.spyOn(customChannel, 'getDraft').mockResolvedValue({}); + customChannel.initialized = true; + customClient.activeChannels[customChannel.cid] = customChannel; + return { customChannel, customClient, getDraftSpy, sendFileSpy, sendImageSpy }; +}; + +const ThreadSetter = () => { + const { openThread } = useChannelActionContext(); + const isOpenThread = useRef(false); + useEffect(() => { + if (isOpenThread.current) return; + isOpenThread.current = true; + openThread(mainListMessage); + }, [openThread]); +}; + +const renderComponent = async ({ + channelData = {}, + channelProps = {}, + chatContextOverrides = {}, + customChannel, + customClient, + customUser, + messageActionsBoxProps = {}, + messageContextOverrides = {}, + messageInputProps = {}, + thread, +} = {}) => { + let channel = customChannel; + let client = customClient; + if (!(channel || client)) { + const result = await initClientWithChannels({ + channelsData: [{ ...mockedChannelData, ...channelData }], + customUser: customUser || user, + }); + channel = result.channels[0]; + client = result.client; + } + let renderResult; + + await act(() => { + renderResult = render( + + + + + + + + + + + , + ); + }); + + const submit = async () => { + const submitButton = + renderResult.findByText('Send') || renderResult.findByTitle('Send'); + fireEvent.click(await submitButton); + }; + + return { channel, client, submit, ...renderResult }; +}; + +describe('MessageInput in Thread', () => { + it('renders in the thread context for direct messaging channel', async () => { + const { customChannel, customClient } = await setup(); + await renderComponent({ + customChannel, + customClient, + }); + expect(screen.getByLabelText('Also send as a direct message')).toBeInTheDocument(); + }); + it('renders in the thread context for non-direct messaging channel', async () => { + const mainListMessage = generateMessage({ cid, user }); + const threadMessage = generateMessage({ + parent_id: mainListMessage.id, + type: 'reply', + user, + }); + + const channelData = generateChannel({ + channel: { + id: 'general', + own_capabilities: ['send-poll', 'upload-file'], + type: 'messaging', + }, + members: [ + generateMember({ user }), + generateMember({ user: mentionUser }), + generateMember({ user: generateUser() }), + ], + // new parent message id has to be provided otherwise the cachedParentMessage in useMessageComposer + // will retrieve the composer from the previous test + messages: [{ ...mainListMessage, id: 'x' }], + thread: [{ ...threadMessage, parent_id: 'x' }], + }); + const { customChannel, customClient } = await setup({ channelData }); + await renderComponent({ + customChannel, + customClient, + thread: channelData.messages[0], + }); + expect(screen.getByLabelText('Also send in channel')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Thread/LegacyThreadContext.ts b/src/components/Thread/LegacyThreadContext.ts new file mode 100644 index 0000000000..39c832a2cd --- /dev/null +++ b/src/components/Thread/LegacyThreadContext.ts @@ -0,0 +1,8 @@ +import React, { useContext } from 'react'; +import type { LocalMessage } from 'stream-chat'; + +export const LegacyThreadContext = React.createContext<{ + legacyThread: LocalMessage | undefined; +}>({ legacyThread: undefined }); + +export const useLegacyThreadContext = () => useContext(LegacyThreadContext); diff --git a/src/components/Thread/Thread.tsx b/src/components/Thread/Thread.tsx index 305cd49760..93bcad2764 100644 --- a/src/components/Thread/Thread.tsx +++ b/src/components/Thread/Thread.tsx @@ -1,6 +1,7 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; import clsx from 'clsx'; +import { LegacyThreadContext } from './LegacyThreadContext'; import { MESSAGE_ACTIONS } from '../Message'; import type { MessageInputProps } from '../MessageInput'; import { MessageInput, MessageInputFlat } from '../MessageInput'; @@ -20,7 +21,7 @@ import { useStateStore } from '../../store'; import type { MessageProps, MessageUIComponentProps } from '../Message/types'; import type { MessageActionsArray } from '../Message/utils'; -import type { LocalMessage, ThreadState } from 'stream-chat'; +import type { ThreadState } from 'stream-chat'; export type ThreadProps = { /** Additional props for `MessageInput` component: [available props](https://getstream.io/chat/docs/sdk/react/message-input-components/message_input/#props) */ @@ -45,12 +46,6 @@ export type ThreadProps = { virtualized?: boolean; }; -const LegacyThreadContext = React.createContext<{ - legacyThread: LocalMessage | undefined; -}>({ legacyThread: undefined }); - -export const useLegacyThreadContext = () => useContext(LegacyThreadContext); - /** * The Thread component renders a parent Message with a list of replies */ diff --git a/src/components/Thread/index.ts b/src/components/Thread/index.ts index 68850ad6a9..25fa274b85 100644 --- a/src/components/Thread/index.ts +++ b/src/components/Thread/index.ts @@ -1,3 +1,4 @@ export * from './Thread'; export * from './ThreadHeader'; export { ThreadStart } from './ThreadStart'; +export { useLegacyThreadContext } from './LegacyThreadContext'; diff --git a/src/context/ComponentContext.tsx b/src/context/ComponentContext.tsx index 9b34e728ae..b99b7de82b 100644 --- a/src/context/ComponentContext.tsx +++ b/src/context/ComponentContext.tsx @@ -124,6 +124,8 @@ export type ComponentContextValue = { MessageBlocked?: React.ComponentType; /** Custom UI component for a deleted message, defaults to and accepts same props as: [MessageDeleted](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageDeleted.tsx) */ MessageDeleted?: React.ComponentType; + /** Custom UI component for an indicator that a message is a thread reply sent to channel list: [MessageIsThreadReplyInChannelButtonIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx) */ + MessageIsThreadReplyInChannelButtonIndicator?: React.ComponentType; MessageListMainPanel?: React.ComponentType; /** Custom UI component that displays message and connection status notifications in the `MessageList`, defaults to and accepts same props as [DefaultMessageListNotifications](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/MessageListNotifications.tsx) */ MessageListNotifications?: React.ComponentType; @@ -196,6 +198,8 @@ export type ComponentContextValue = { SearchSourceResultsLoadingIndicator?: React.ComponentType; /** Custom UI component for send button, defaults to and accepts same props as: [SendButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/icons.tsx) */ SendButton?: React.ComponentType; + /** Custom UI component checkbox that indicates message to be sent to main channel, defaults to and accepts same props as: [SendToChannelCheckbox](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/SendToChannelCheckbox.tsx) */ + SendToChannelCheckbox?: React.ComponentType; /** Custom UI component button for initiating audio recording, defaults to and accepts same props as: [StartRecordingAudioButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MediaRecorder/AudioRecorder/AudioRecordingButtons.tsx) */ StartRecordingAudioButton?: React.ComponentType; StopAIGenerationButton?: React.ComponentType | null; diff --git a/src/i18n/de.json b/src/i18n/de.json index b57e471526..e2f140371f 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Zugriff auf Mikrofon erlauben", "Allow comments": "Kommentare erlauben", "Allow option suggestion": "Optionsvorschläge erlauben", + "Also send as a direct message": "Auch als Direktnachricht senden", + "Also send in channel": "Auch im Kanal senden", "An error has occurred during recording": "Ein Fehler ist während der Aufnahme aufgetreten", "An error has occurred during the recording processing": "Ein Fehler ist während der Aufnahmeverarbeitung aufgetreten", "Anonymous": "Anonym", @@ -118,6 +120,7 @@ "This message did not meet our content guidelines": "Diese Nachricht entsprach nicht unseren Inhaltsrichtlinien", "This message was deleted...": "Diese Nachricht wurde gelöscht...", "Thread": "Thread", + "Thread reply": "Thread-Antwort", "To start recording, allow the camera access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf die Kamera in Ihrem Browser", "To start recording, allow the microphone access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf das Mikrofon in Ihrem Browser", "Type a number from 2 to 10": "Geben Sie eine Zahl von 2 bis 10 ein", diff --git a/src/i18n/en.json b/src/i18n/en.json index dd0ec0df8a..2325677589 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Allow access to microphone", "Allow comments": "Allow comments", "Allow option suggestion": "Allow option suggestion", + "Also send as a direct message": "Also send as a direct message", + "Also send in channel": "Also send in channel", "An error has occurred during recording": "An error has occurred during recording", "An error has occurred during the recording processing": "An error has occurred during the recording processing", "Anonymous": "Anonymous", @@ -118,6 +120,7 @@ "This message did not meet our content guidelines": "This message did not meet our content guidelines", "This message was deleted...": "This message was deleted...", "Thread": "Thread", + "Thread reply": "Thread reply", "To start recording, allow the camera access in your browser": "To start recording, allow the camera access in your browser", "To start recording, allow the microphone access in your browser": "To start recording, allow the microphone access in your browser", "Type a number from 2 to 10": "Type a number from 2 to 10", diff --git a/src/i18n/es.json b/src/i18n/es.json index c59165beed..80742b790c 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Permitir acceso al micrófono", "Allow comments": "Permitir comentarios", "Allow option suggestion": "Permitir sugerencia de opciones", + "Also send as a direct message": "También enviar como mensaje directo", + "Also send in channel": "También enviar en el canal", "An error has occurred during recording": "Se ha producido un error durante la grabación", "An error has occurred during the recording processing": "Se ha producido un error durante el procesamiento de la grabación", "Anonymous": "Anónimo", @@ -120,6 +122,7 @@ "This message did not meet our content guidelines": "Este mensaje no cumple con nuestras directrices de contenido", "This message was deleted...": "Este mensaje fue eliminado...", "Thread": "Hilo", + "Thread reply": "Respuesta en hilo", "To start recording, allow the camera access in your browser": "Para comenzar a grabar, permita el acceso a la cámara en su navegador", "To start recording, allow the microphone access in your browser": "Para comenzar a grabar, permita el acceso al micrófono en su navegador", "Type a number from 2 to 10": "Escribe un número del 2 al 10", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 8634d30ccc..a6bbe5bbf5 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Autoriser l'accès au microphone", "Allow comments": "Autoriser les commentaires", "Allow option suggestion": "Autoriser la suggestion d'options", + "Also send as a direct message": "Également envoyer en message direct", + "Also send in channel": "Également envoyer dans le canal", "An error has occurred during recording": "Une erreur s'est produite pendant l'enregistrement", "An error has occurred during the recording processing": "Une erreur s'est produite pendant le traitement de l'enregistrement", "Anonymous": "Anonyme", @@ -120,6 +122,7 @@ "This message did not meet our content guidelines": "Ce message ne respecte pas nos directives de contenu", "This message was deleted...": "Ce message a été supprimé...", "Thread": "Fil de discussion", + "Thread reply": "Réponse dans le fil", "To start recording, allow the camera access in your browser": "Pour commencer l'enregistrement, autorisez l'accès à la caméra dans votre navigateur", "To start recording, allow the microphone access in your browser": "Pour commencer l'enregistrement, autorisez l'accès au microphone dans votre navigateur", "Type a number from 2 to 10": "Tapez un nombre de 2 à 10", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index 0271c652e1..578007f3f2 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -6,6 +6,8 @@ "Allow access to microphone": "माइक्रोफ़ोन तक पहुँच दें", "Allow comments": "टिप्पणियाँ की अनुमति दें", "Allow option suggestion": "विकल्प सुझाव की अनुमति दें", + "Also send as a direct message": "सीधे संदेश के रूप में भी भेजें", + "Also send in channel": "चैनल में भी भेजें", "An error has occurred during recording": "रेकॉर्डिंग के दौरान एक त्रुटि आ गई है", "An error has occurred during the recording processing": "रेकॉर्डिंग प्रोसेसिंग के दौरान एक त्रुटि आ गई है", "Anonymous": "गुमनाम", @@ -119,6 +121,7 @@ "This message did not meet our content guidelines": "यह संदेश हमारे सामग्री दिशानिर्देशों के अनुरूप नहीं था", "This message was deleted...": "मैसेज हटा दिया गया", "Thread": "रिप्लाई थ्रेड", + "Thread reply": "थ्रेड में उत्तर", "To start recording, allow the camera access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में कैमरा तक पहुँच दें", "To start recording, allow the microphone access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में माइक्रोफ़ोन तक पहुँच दें", "Type a number from 2 to 10": "2 से 10 तक का एक नंबर टाइप करें", diff --git a/src/i18n/it.json b/src/i18n/it.json index 8aa9468008..813e174dea 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Consenti l'accesso al microfono", "Allow comments": "Consenti i commenti", "Allow option suggestion": "Consenti il suggerimento di opzioni", + "Also send as a direct message": "Invia anche come messaggio diretto", + "Also send in channel": "Invia anche nel canale", "An error has occurred during recording": "Si è verificato un errore durante la registrazione", "An error has occurred during the recording processing": "Si è verificato un errore durante l'elaborazione della registrazione", "Anonymous": "Anonimo", @@ -120,6 +122,7 @@ "This message did not meet our content guidelines": "Questo messaggio non soddisfa le nostre linee guida sui contenuti", "This message was deleted...": "Questo messaggio è stato cancellato...", "Thread": "Discussione", + "Thread reply": "Risposta nella discussione", "To start recording, allow the camera access in your browser": "Per iniziare a registrare, consenti l'accesso alla fotocamera nel tuo browser", "To start recording, allow the microphone access in your browser": "Per iniziare a registrare, consenti l'accesso al microfono nel tuo browser", "Type a number from 2 to 10": "Digita un numero da 2 a 10", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index eced5707be..b5ff616b8e 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -6,6 +6,8 @@ "Allow access to microphone": "マイクロフォンへのアクセスを許可する", "Allow comments": "コメントを許可", "Allow option suggestion": "オプションの提案を許可", + "Also send as a direct message": "ダイレクトメッセージとしても送信", + "Also send in channel": "チャンネルにも送信", "An error has occurred during recording": "録音中にエラーが発生しました", "An error has occurred during the recording processing": "録音処理中にエラーが発生しました", "Anonymous": "匿名", @@ -116,6 +118,7 @@ "This message did not meet our content guidelines": "このメッセージはコンテンツガイドラインに適合していません", "This message was deleted...": "このメッセージは削除されました...", "Thread": "スレッド", + "Thread reply": "スレッドの返信", "To start recording, allow the camera access in your browser": "録音を開始するには、ブラウザーでカメラへのアクセスを許可してください", "To start recording, allow the microphone access in your browser": "録音を開始するには、ブラウザーでマイクロフォンへのアクセスを許可してください", "Type a number from 2 to 10": "2から10までの数字を入力してください", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index a42a75d3d7..a44e5ce6c5 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -6,6 +6,8 @@ "Allow access to microphone": "마이크로폰에 대한 액세스 허용", "Allow comments": "댓글 허용", "Allow option suggestion": "옵션 제안 허용", + "Also send as a direct message": "다이렉트 메시지로도 보내기", + "Also send in channel": "채널에도 보내기", "An error has occurred during recording": "녹음 중 오류가 발생했습니다", "An error has occurred during the recording processing": "녹음 처리 중 오류가 발생했습니다", "Anonymous": "익명", @@ -116,6 +118,7 @@ "This message did not meet our content guidelines": "이 메시지는 콘텐츠 가이드라인을 충족하지 않습니다.", "This message was deleted...": "이 메시지는 삭제되었습니다...", "Thread": "스레드", + "Thread reply": "스레드 답장", "To start recording, allow the camera access in your browser": "브라우저에서 카메라 액세스를 허용하여 녹음을 시작합니다", "To start recording, allow the microphone access in your browser": "브라우저에서 마이크로폰 액세스를 허용하여 녹음을 시작합니다", "Type a number from 2 to 10": "2에서 10 사이의 숫자를 입력하세요", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index c68452dc78..496371f282 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Toegang tot microfoon toestaan", "Allow comments": "Sta opmerkingen toe", "Allow option suggestion": "Sta optie-suggesties toe", + "Also send as a direct message": "Ook als direct bericht versturen", + "Also send in channel": "Ook in kanaal versturen", "An error has occurred during recording": "Er is een fout opgetreden tijdens het opnemen", "An error has occurred during the recording processing": "Er is een fout opgetreden tijdens de verwerking van de opname", "Anonymous": "Anoniem", @@ -118,6 +120,7 @@ "This message did not meet our content guidelines": "Dit bericht voldeed niet aan onze inhoudsrichtlijnen", "This message was deleted...": "Dit bericht was verwijderd", "Thread": "Draadje", + "Thread reply": "Draadje antwoord", "To start recording, allow the camera access in your browser": "Om te beginnen met opnemen, sta toegang tot de camera toe in uw browser", "To start recording, allow the microphone access in your browser": "Om te beginnen met opnemen, sta toegang tot de microfoon toe in uw browser", "Type a number from 2 to 10": "Typ een getal van 2 tot 10", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index d7a01f547d..b307c8aaaa 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Permitir acesso ao microfone", "Allow comments": "Permitir comentários", "Allow option suggestion": "Permitir sugestão de opção", + "Also send as a direct message": "Também enviar como mensagem direta", + "Also send in channel": "Também enviar no canal", "An error has occurred during recording": "Ocorreu um erro durante a gravação", "An error has occurred during the recording processing": "Ocorreu um erro durante o processamento da gravação", "Anonymous": "Anônimo", @@ -120,6 +122,7 @@ "This message did not meet our content guidelines": "Esta mensagem não corresponde às nossas diretrizes de conteúdo", "This message was deleted...": "Esta mensagem foi excluída...", "Thread": "Fio", + "Thread reply": "Resposta no fio", "To start recording, allow the camera access in your browser": "Para começar a gravar, permita o acesso à câmera no seu navegador", "To start recording, allow the microphone access in your browser": "Para começar a gravar, permita o acesso ao microfone no seu navegador", "Type a number from 2 to 10": "Digite um número de 2 a 10", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index ad5856beb7..51da9b2dfc 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Разрешить доступ к микрофону", "Allow comments": "Разрешить комментарии", "Allow option suggestion": "Разрешить предложение вариантов", + "Also send as a direct message": "Также отправить как личное сообщение", + "Also send in channel": "Также отправить в канал", "An error has occurred during recording": "Произошла ошибка во время записи", "An error has occurred during the recording processing": "Произошла ошибка во время обработки записи", "Anonymous": "Аноним", @@ -122,6 +124,7 @@ "This message did not meet our content guidelines": "Сообщение не соответствует правилам", "This message was deleted...": "Сообщение было удалено...", "Thread": "Ветка", + "Thread reply": "Ответ в ветке", "To start recording, allow the camera access in your browser": "Для начала записи разрешите доступ к камере в вашем браузере", "To start recording, allow the microphone access in your browser": "Для начала записи разрешите доступ к микрофону в вашем браузере", "Type a number from 2 to 10": "Введите число от 2 до 10", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index a703ddafc2..52edc425ef 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -6,6 +6,8 @@ "Allow access to microphone": "Mikrofona erişime izin ver", "Allow comments": "Yorumlara izin ver", "Allow option suggestion": "Seçenek önerisine izin ver", + "Also send as a direct message": "Ayrıca doğrudan mesaj olarak gönder", + "Also send in channel": "Ayrıca kanala gönder", "An error has occurred during recording": "Kayıt sırasında bir hata oluştu", "An error has occurred during the recording processing": "Kayıt işlemi sırasında bir hata oluştu", "Anonymous": "Anonim", @@ -118,6 +120,7 @@ "This message did not meet our content guidelines": "Bu mesaj içerik yönergelerimize uygun değil", "This message was deleted...": "Bu mesaj silindi...", "Thread": "Konu", + "Thread reply": "Konu yanıtı", "To start recording, allow the camera access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda kameraya erişime izin verin", "To start recording, allow the microphone access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda mikrofona erişime izin verin", "Type a number from 2 to 10": "2 ile 10 arasında bir sayı yazın", From 6dab1391179d45573ef48dfebab916500fde12b6 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Jun 2025 10:52:54 +0200 Subject: [PATCH 2/5] feat: add error handling for thread search in MessageThreadReplyInChannelButtonIndicator --- src/components/Message/MessageSimple.tsx | 2 +- ...geThreadReplyInChannelButtonIndicator.tsx} | 22 +++++++++++++++++-- src/context/ComponentContext.tsx | 2 +- src/i18n/de.json | 1 + src/i18n/en.json | 1 + src/i18n/es.json | 1 + src/i18n/fr.json | 1 + src/i18n/hi.json | 1 + src/i18n/it.json | 1 + src/i18n/ja.json | 1 + src/i18n/ko.json | 1 + src/i18n/nl.json | 1 + src/i18n/pt.json | 1 + src/i18n/ru.json | 1 + src/i18n/tr.json | 1 + 15 files changed, 34 insertions(+), 4 deletions(-) rename src/components/Message/{MessageIsThreadReplyInChannelButtonIndicator.tsx => MessageThreadReplyInChannelButtonIndicator.tsx} (75%) diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index 8831626e4e..c8c89c203d 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -12,7 +12,7 @@ import { MessageText } from './MessageText'; import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp'; import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText'; import { isDateSeparatorMessage } from '../MessageList'; -import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator'; +import { MessageThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageThreadReplyInChannelButtonIndicator'; import { areMessageUIPropsEqual, isMessageBlocked, diff --git a/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx b/src/components/Message/MessageThreadReplyInChannelButtonIndicator.tsx similarity index 75% rename from src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx rename to src/components/Message/MessageThreadReplyInChannelButtonIndicator.tsx index bf526a6428..062d886cfb 100644 --- a/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx +++ b/src/components/Message/MessageThreadReplyInChannelButtonIndicator.tsx @@ -4,11 +4,13 @@ import { formatMessage } from 'stream-chat'; import { useChannelActionContext, useChannelStateContext, + useChatContext, useMessageContext, useTranslationContext, } from '../../context'; -export const MessageIsThreadReplyInChannelButtonIndicator = () => { +export const MessageThreadReplyInChannelButtonIndicator = () => { + const { client } = useChatContext(); const { t } = useTranslationContext(); const { channel } = useChannelStateContext(); const { openThread } = useChannelActionContext(); @@ -20,8 +22,23 @@ export const MessageIsThreadReplyInChannelButtonIndicator = () => { .getClient() .search({ cid: channel.cid }, { id: message.parent_id }) .then(({ results }) => { - if (!results.length) return; + if (!results.length) { + throw new Error('Thread has not been found'); + } parentMessageRef.current = formatMessage(results[0].message); + }) + .catch((error: Error) => { + client.notifications.addError({ + message: t('Thread has not been found'), + options: { + originalError: error, + type: 'api:message:search:not-found', + }, + origin: { + context: { threadReply: message }, + emitter: 'MessageThreadReplyInChannelButtonIndicator', + }, + }); }); useEffect(() => { @@ -60,6 +77,7 @@ export const MessageIsThreadReplyInChannelButtonIndicator = () => { } openThread(parentMessageRef.current); }} + type='button' > {t('Thread reply')} diff --git a/src/context/ComponentContext.tsx b/src/context/ComponentContext.tsx index b99b7de82b..c3e516a7f0 100644 --- a/src/context/ComponentContext.tsx +++ b/src/context/ComponentContext.tsx @@ -124,7 +124,7 @@ export type ComponentContextValue = { MessageBlocked?: React.ComponentType; /** Custom UI component for a deleted message, defaults to and accepts same props as: [MessageDeleted](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageDeleted.tsx) */ MessageDeleted?: React.ComponentType; - /** Custom UI component for an indicator that a message is a thread reply sent to channel list: [MessageIsThreadReplyInChannelButtonIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx) */ + /** Custom UI component for an indicator that a message is a thread reply sent to channel list: [MessageThreadReplyInChannelButtonIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx) */ MessageIsThreadReplyInChannelButtonIndicator?: React.ComponentType; MessageListMainPanel?: React.ComponentType; /** Custom UI component that displays message and connection status notifications in the `MessageList`, defaults to and accepts same props as [DefaultMessageListNotifications](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/MessageListNotifications.tsx) */ diff --git a/src/i18n/de.json b/src/i18n/de.json index e2f140371f..968d17aab1 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -120,6 +120,7 @@ "This message did not meet our content guidelines": "Diese Nachricht entsprach nicht unseren Inhaltsrichtlinien", "This message was deleted...": "Diese Nachricht wurde gelöscht...", "Thread": "Thread", + "Thread has not been found": "Thread wurde nicht gefunden", "Thread reply": "Thread-Antwort", "To start recording, allow the camera access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf die Kamera in Ihrem Browser", "To start recording, allow the microphone access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf das Mikrofon in Ihrem Browser", diff --git a/src/i18n/en.json b/src/i18n/en.json index 2325677589..e7b90ba402 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -120,6 +120,7 @@ "This message did not meet our content guidelines": "This message did not meet our content guidelines", "This message was deleted...": "This message was deleted...", "Thread": "Thread", + "Thread has not been found": "Thread has not been found", "Thread reply": "Thread reply", "To start recording, allow the camera access in your browser": "To start recording, allow the camera access in your browser", "To start recording, allow the microphone access in your browser": "To start recording, allow the microphone access in your browser", diff --git a/src/i18n/es.json b/src/i18n/es.json index 80742b790c..31cf8a800d 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -122,6 +122,7 @@ "This message did not meet our content guidelines": "Este mensaje no cumple con nuestras directrices de contenido", "This message was deleted...": "Este mensaje fue eliminado...", "Thread": "Hilo", + "Thread has not been found": "No se ha encontrado el hilo", "Thread reply": "Respuesta en hilo", "To start recording, allow the camera access in your browser": "Para comenzar a grabar, permita el acceso a la cámara en su navegador", "To start recording, allow the microphone access in your browser": "Para comenzar a grabar, permita el acceso al micrófono en su navegador", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index a6bbe5bbf5..732c6e3c2e 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -122,6 +122,7 @@ "This message did not meet our content guidelines": "Ce message ne respecte pas nos directives de contenu", "This message was deleted...": "Ce message a été supprimé...", "Thread": "Fil de discussion", + "Thread has not been found": "Le fil de discussion n'a pas été trouvé", "Thread reply": "Réponse dans le fil", "To start recording, allow the camera access in your browser": "Pour commencer l'enregistrement, autorisez l'accès à la caméra dans votre navigateur", "To start recording, allow the microphone access in your browser": "Pour commencer l'enregistrement, autorisez l'accès au microphone dans votre navigateur", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index 578007f3f2..949d1ec15d 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -121,6 +121,7 @@ "This message did not meet our content guidelines": "यह संदेश हमारे सामग्री दिशानिर्देशों के अनुरूप नहीं था", "This message was deleted...": "मैसेज हटा दिया गया", "Thread": "रिप्लाई थ्रेड", + "Thread has not been found": "थ्रेड नहीं मिला", "Thread reply": "थ्रेड में उत्तर", "To start recording, allow the camera access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में कैमरा तक पहुँच दें", "To start recording, allow the microphone access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में माइक्रोफ़ोन तक पहुँच दें", diff --git a/src/i18n/it.json b/src/i18n/it.json index 813e174dea..6a2440506d 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -122,6 +122,7 @@ "This message did not meet our content guidelines": "Questo messaggio non soddisfa le nostre linee guida sui contenuti", "This message was deleted...": "Questo messaggio è stato cancellato...", "Thread": "Discussione", + "Thread has not been found": "Discussione non trovata", "Thread reply": "Risposta nella discussione", "To start recording, allow the camera access in your browser": "Per iniziare a registrare, consenti l'accesso alla fotocamera nel tuo browser", "To start recording, allow the microphone access in your browser": "Per iniziare a registrare, consenti l'accesso al microfono nel tuo browser", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index b5ff616b8e..9e9a1d4639 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -118,6 +118,7 @@ "This message did not meet our content guidelines": "このメッセージはコンテンツガイドラインに適合していません", "This message was deleted...": "このメッセージは削除されました...", "Thread": "スレッド", + "Thread has not been found": "スレッドが見つかりませんでした", "Thread reply": "スレッドの返信", "To start recording, allow the camera access in your browser": "録音を開始するには、ブラウザーでカメラへのアクセスを許可してください", "To start recording, allow the microphone access in your browser": "録音を開始するには、ブラウザーでマイクロフォンへのアクセスを許可してください", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index a44e5ce6c5..7e37d39374 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -118,6 +118,7 @@ "This message did not meet our content guidelines": "이 메시지는 콘텐츠 가이드라인을 충족하지 않습니다.", "This message was deleted...": "이 메시지는 삭제되었습니다...", "Thread": "스레드", + "Thread has not been found": "스레드를 찾을 수 없습니다", "Thread reply": "스레드 답장", "To start recording, allow the camera access in your browser": "브라우저에서 카메라 액세스를 허용하여 녹음을 시작합니다", "To start recording, allow the microphone access in your browser": "브라우저에서 마이크로폰 액세스를 허용하여 녹음을 시작합니다", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 496371f282..2f24f9e440 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -120,6 +120,7 @@ "This message did not meet our content guidelines": "Dit bericht voldeed niet aan onze inhoudsrichtlijnen", "This message was deleted...": "Dit bericht was verwijderd", "Thread": "Draadje", + "Thread has not been found": "Draadje niet gevonden", "Thread reply": "Draadje antwoord", "To start recording, allow the camera access in your browser": "Om te beginnen met opnemen, sta toegang tot de camera toe in uw browser", "To start recording, allow the microphone access in your browser": "Om te beginnen met opnemen, sta toegang tot de microfoon toe in uw browser", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index b307c8aaaa..971e7b9e22 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -122,6 +122,7 @@ "This message did not meet our content guidelines": "Esta mensagem não corresponde às nossas diretrizes de conteúdo", "This message was deleted...": "Esta mensagem foi excluída...", "Thread": "Fio", + "Thread has not been found": "Fio não encontrado", "Thread reply": "Resposta no fio", "To start recording, allow the camera access in your browser": "Para começar a gravar, permita o acesso à câmera no seu navegador", "To start recording, allow the microphone access in your browser": "Para começar a gravar, permita o acesso ao microfone no seu navegador", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 51da9b2dfc..d092adc9f1 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -124,6 +124,7 @@ "This message did not meet our content guidelines": "Сообщение не соответствует правилам", "This message was deleted...": "Сообщение было удалено...", "Thread": "Ветка", + "Thread has not been found": "Ветка не найдена", "Thread reply": "Ответ в ветке", "To start recording, allow the camera access in your browser": "Для начала записи разрешите доступ к камере в вашем браузере", "To start recording, allow the microphone access in your browser": "Для начала записи разрешите доступ к микрофону в вашем браузере", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index 52edc425ef..d50ffb7fd9 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -120,6 +120,7 @@ "This message did not meet our content guidelines": "Bu mesaj içerik yönergelerimize uygun değil", "This message was deleted...": "Bu mesaj silindi...", "Thread": "Konu", + "Thread has not been found": "Konu bulunamadı", "Thread reply": "Konu yanıtı", "To start recording, allow the camera access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda kameraya erişime izin verin", "To start recording, allow the microphone access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda mikrofona erişime izin verin", From 5a13b722b345a8482d9d79995389e4ff9fb453f2 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Jun 2025 10:59:10 +0200 Subject: [PATCH 3/5] chore(deps): upgrade stream-chat-css to v5.11.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ab44db4ca6..47056bf584 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "@playwright/test": "^1.42.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", - "@stream-io/stream-chat-css": "^5.9.3", + "@stream-io/stream-chat-css": "^5.11.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", diff --git a/yarn.lock b/yarn.lock index d6bbe7b68c..ec16115c4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2438,10 +2438,10 @@ resolved "https://registry.yarnpkg.com/@stream-io/escape-string-regexp/-/escape-string-regexp-5.0.1.tgz#362505c92799fea6afe4e369993fbbda8690cc37" integrity sha512-qIaSrzJXieZqo2fZSYTdzwSbZgHHsT3tkd646vvZhh4fr+9nO4NlvqGmPF43Y+OfZiWf+zYDFgNiPGG5+iZulQ== -"@stream-io/stream-chat-css@^5.9.3": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@stream-io/stream-chat-css/-/stream-chat-css-5.9.3.tgz#7300e85581cd9d9c795433e4575cfbdacecd98cf" - integrity sha512-3vK6uZPkqghk/Y0UYlSw9JjM90kDk8RHBfESVZafq+31KdKvjsj3SpXhz8MRYh+xY2lktNgBwW9G4kazUQKdjQ== +"@stream-io/stream-chat-css@^5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@stream-io/stream-chat-css/-/stream-chat-css-5.11.0.tgz#9ca86d4539400133b766fdf3e02256dbdce22c78" + integrity sha512-+yS+TB+g7uD0D0OyHIz5h0EIlWYjrhtu8hmdkvjhh3xlcdBX+mMANviO6gRZ2CYKYAtivqbmShawc5USzaCyjg== "@stream-io/transliterate@^1.5.5": version "1.5.5" From dbcc3f88a0447556772477af77a2c2d327ff10ed Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Jun 2025 11:06:17 +0200 Subject: [PATCH 4/5] chore(deps): upgrade stream-chat to v9.6.0 --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 47056bf584..d8fd278536 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "emoji-mart": "^5.4.0", "react": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0", "react-dom": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0", - "stream-chat": "^9.0.0" + "stream-chat": "^9.6.0" }, "peerDependenciesMeta": { "@breezystack/lamejs": { @@ -237,7 +237,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "semantic-release": "^24.2.3", - "stream-chat": "9.1.1", + "stream-chat": "^9.6.0", "ts-jest": "^29.2.5", "typescript": "^5.4.5", "typescript-eslint": "^8.17.0" diff --git a/yarn.lock b/yarn.lock index ec16115c4b..946882bc5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12155,10 +12155,10 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -stream-chat@9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.1.1.tgz#c81ffa84a7ca579d9812065bc159010191b59090" - integrity sha512-7Y23aIVQMppNZgRj/rTFwIx9pszxgDcS99idkSXJSgdV8C7FlyDtiF1yQSdP0oiNFAt7OUP/xSqmbJTljrm24Q== +stream-chat@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.6.0.tgz#d281e0c6a440d9590ae344883a9a74caac13caf2" + integrity sha512-up2PmyVKeuxe28SC1piEgagwNsMYEi4Xz3PN6oKsiLa7FXzNhgvjuGXQtgGNuo2Qwg39UsrdkhOxvAPwkvi5Kw== dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14" From efa00c4681daae929d8ffa2b7aa31e8655363e09 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Jun 2025 11:13:10 +0200 Subject: [PATCH 5/5] test: adjust test to stream-chat changes --- src/components/MessageInput/__tests__/EditMessageForm.test.js | 4 ++++ src/components/Poll/__tests__/PollCreationDialog.test.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/MessageInput/__tests__/EditMessageForm.test.js b/src/components/MessageInput/__tests__/EditMessageForm.test.js index dc0c408ead..c9a21996b3 100644 --- a/src/components/MessageInput/__tests__/EditMessageForm.test.js +++ b/src/components/MessageInput/__tests__/EditMessageForm.test.js @@ -1177,6 +1177,8 @@ describe(`EditMessageForm`, () => { await act(() => submit()); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { mutes, ...userWithoutMutes } = mainListMessage.user; expect(editMock.mock.calls[1]).toEqual([ customChannel.cid, expect.objectContaining({ @@ -1199,6 +1201,8 @@ describe(`EditMessageForm`, () => { quoted_message: null, reaction_groups: null, text: '@mention-name ', + user: userWithoutMutes, + user_id: 'userId', }), {}, ]); diff --git a/src/components/Poll/__tests__/PollCreationDialog.test.js b/src/components/Poll/__tests__/PollCreationDialog.test.js index b7ba9f62f1..3330ef03e8 100644 --- a/src/components/Poll/__tests__/PollCreationDialog.test.js +++ b/src/components/Poll/__tests__/PollCreationDialog.test.js @@ -100,7 +100,7 @@ describe('PollCreationDialog', () => { await fireEvent.blur(nameInput); }); expect(screen.getByTestId(NAME_INPUT_FIELD_ERROR_TEST_ID)).toHaveTextContent( - 'Name is required', + 'Question is required', ); expect(nameInput).toHaveValue(''); expect(screen.getByText(CANCEL_BUTTON_TEXT)).toBeEnabled();