diff --git a/src/components/Message/MessageAlsoSentInChannelIndicator.tsx b/src/components/Message/MessageAlsoSentInChannelIndicator.tsx index 6f7929768..f3f8643e1 100644 --- a/src/components/Message/MessageAlsoSentInChannelIndicator.tsx +++ b/src/components/Message/MessageAlsoSentInChannelIndicator.tsx @@ -1,22 +1,104 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { IconArrowRightUp } from '../Icons'; -import { useMessageContext, useTranslationContext } from '../../context'; +import { + useChannelActionContext, + useChannelStateContext, + useChatContext, + useMessageContext, + useTranslationContext, +} from '../../context'; +import { formatMessage, type LocalMessage } from 'stream-chat'; /** - * Indicator shown in thread message lists when the message was also sent to the main channel (show_in_channel === true). - * Only visible inside Thread, not in the main channel list. + * Indicator shown when the message was also sent to the main channel (show_in_channel === true). */ export const MessageAlsoSentInChannelIndicator = () => { - const { message, threadList } = useMessageContext('MessageAlsoSentInChannelIndicator'); + const { client } = useChatContext(); const { t } = useTranslationContext(); + const { channel } = useChannelStateContext(); + const { jumpToMessage, openThread } = useChannelActionContext(); + const { message, threadList } = useMessageContext('MessageAlsoSentInChannelIndicator'); + const targetMessageRef = useRef(undefined); + + const queryParent = () => + channel + .getClient() + .search({ cid: channel.cid }, { id: message.parent_id }) + .then(({ results }) => { + if (!results.length) { + throw new Error('Thread has not been found'); + } + targetMessageRef.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: 'MessageIsThreadReplyInChannelButtonIndicator', + }, + }); + }); + + // todo: it is not possible to jump to a message in thread + const jumpToReplyInChannelMessages = async (id: string) => { + await jumpToMessage(id); + // todo: we do not have API to control, whether thread of channel message list is show - on mobile devices important + }; + + useEffect(() => { + if ( + targetMessageRef.current || + targetMessageRef.current === null || + !message.parent_id + ) + return; + const localMessage = channel.state.findMessage(message.parent_id); + if (localMessage) { + targetMessageRef.current = localMessage; + return; + } + }, [channel, message]); + + const handleClickViewReference = async () => { + if (!targetMessageRef.current) { + // search query is performed here in order to prevent multiple search queries in useEffect + // due to the message list 3x remounting its items + if (threadList) { + await jumpToReplyInChannelMessages(message.id); // we are in thread, and we want to jump to this reply in the main message list + return; + } else await queryParent(); // we are in the main list and need to query the thread + } + const target = targetMessageRef.current; + if (!target) { + // prevent further search queries if the message is not found in the DB + targetMessageRef.current = null; + return; + } + + if (threadList) await jumpToReplyInChannelMessages(message.id); + else openThread(target); + }; - if (!threadList || !message?.show_in_channel) return null; + if (!message?.show_in_channel) return null; return (
- {t('Also sent in channel')} + {threadList ? t('Also sent in channel') : t('Replied to a thread')} + · +
); }; diff --git a/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx b/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx deleted file mode 100644 index 6b9f3f379..000000000 --- a/src/components/Message/MessageIsThreadReplyInChannelButtonIndicator.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import type { LocalMessage } from 'stream-chat'; -import { formatMessage } from 'stream-chat'; -import { - useChannelActionContext, - useChannelStateContext, - useChatContext, - useMessageContext, - useTranslationContext, -} from '../../context'; - -export const MessageIsThreadReplyInChannelButtonIndicator = () => { - const { client } = useChatContext(); - 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) { - 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: 'MessageIsThreadReplyInChannelButtonIndicator', - }, - }); - }); - - 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 f1e4048ff..03642f5ba 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -13,7 +13,6 @@ import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp' import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText'; import { isDateSeparatorMessage } from '../MessageList'; import { MessageAlsoSentInChannelIndicator as DefaultMessageAlsoSentInChannelIndicator } from './MessageAlsoSentInChannelIndicator'; -import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator'; import { ReminderNotification as DefaultReminderNotification } from './ReminderNotification'; import { MessageTranslationIndicator as DefaultMessageTranslationIndicator } from './MessageTranslationIndicator'; import { useMessageReminder } from './hooks'; @@ -86,7 +85,6 @@ const MessageSimpleWithContext = ({ MessageBouncePrompt = DefaultMessageBouncePrompt, MessageDeleted, MessageDeletedBubble = DefaultMessageDeletedBubble, - MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, @@ -141,8 +139,6 @@ const MessageSimpleWithContext = ({ const showMetadata = !groupedByUser || endOfGroup; const showReplyCountButton = !threadList && !!message.reply_count; - const showIsReplyInChannel = - !threadList && message.show_in_channel && message.parent_id; const rootClassName = clsx( 'str-chat__message str-chat__message-simple', @@ -171,7 +167,7 @@ const MessageSimpleWithContext = ({ '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 || showIsReplyInChannel, + 'str-chat__message-with-thread-link': showReplyCountButton, 'str-chat__virtual-message__wrapper--end': endOfGroup, 'str-chat__virtual-message__wrapper--first': firstOfGroup, 'str-chat__virtual-message__wrapper--group': groupedByUser, @@ -197,7 +193,7 @@ const MessageSimpleWithContext = ({ )}
{message.pinned && } - {threadList && message.show_in_channel && } + {message.show_in_channel && } {!!reminder && } {message.user && ( @@ -251,7 +247,6 @@ const MessageSimpleWithContext = ({ thread_participants={message.thread_participants} /> )} - {showIsReplyInChannel && }
{showMetadata && (
diff --git a/src/components/Message/index.ts b/src/components/Message/index.ts index c69a7e639..e58963f32 100644 --- a/src/components/Message/index.ts +++ b/src/components/Message/index.ts @@ -5,7 +5,6 @@ export * from './MessageBlocked'; export * from './MessageDeletedBubble'; export * from './MessageEditedTimestamp'; export * from './MessageAlsoSentInChannelIndicator'; -export * from './MessageIsThreadReplyInChannelButtonIndicator'; export * from './MessageRepliesCountButton'; export * from './PinIndicator'; export * from './MessageSimple'; diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index 120b2ca4c..cac4e5ab1 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -272,9 +272,7 @@ justify-content: flex-end; } - &.str-chat__message--me - .str-chat__message-also-sent-in-channel - .str-chat__message-also-sent-in-channel__content { + &.str-chat__message--me .str-chat__message-also-sent-in-channel { justify-content: flex-end; } diff --git a/src/components/Message/styling/MessageAlsoSentInChannelIndicator.scss b/src/components/Message/styling/MessageAlsoSentInChannelIndicator.scss index 9a5f61a57..2ea2c7764 100644 --- a/src/components/Message/styling/MessageAlsoSentInChannelIndicator.scss +++ b/src/components/Message/styling/MessageAlsoSentInChannelIndicator.scss @@ -24,4 +24,11 @@ stroke-width: 1.5px; stroke: var(--str-chat__message-also-sent-in-channel-color); } + + .str-chat__message-also-sent-in-channel__link-button { + @include utils.button-reset; + cursor: pointer; + font: var(--str-chat__metadata-default-text); + color: var(--text-link); + } } diff --git a/src/context/ComponentContext.tsx b/src/context/ComponentContext.tsx index 1466e3b2d..6b765a72d 100644 --- a/src/context/ComponentContext.tsx +++ b/src/context/ComponentContext.tsx @@ -143,8 +143,6 @@ export type ComponentContextValue = { MessageDeleted?: React.ComponentType; /** Custom UI component for a message bubble of a deleted message, defaults to and accepts same props as: [MessageDeletedBubble](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageDeletedBubble.tsx) */ MessageDeletedBubble?: 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; diff --git a/src/i18n/de.json b/src/i18n/de.json index 9ef64503f..7e9daa5d2 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -288,6 +288,7 @@ "Reminder set": "Erinnerung gesetzt", "Remove reminder": "Erinnerung entfernen", "Remove save for later": "„Später ansehen“ entfernen", + "Replied to a thread": "In einem Thread geantwortet", "Reply": "Antworten", "Reply to {{ authorName }}": "Antwort an {{ authorName }}", "Reply to Message": "Auf Nachricht antworten", @@ -377,6 +378,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Upload-Typ: \"{{ type }}\" ist nicht erlaubt", "User uploaded content": "Vom Benutzer hochgeladener Inhalt", "Video": "Video", + "View": "Ansehen", "View {{count}} comments_one": "{{count}} Kommentar anzeigen", "View {{count}} comments_other": "{{count}} Kommentare anzeigen", "View original": "Original anzeigen", diff --git a/src/i18n/en.json b/src/i18n/en.json index 7faa0b27a..953df41b3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -288,6 +288,7 @@ "Reminder set": "Reminder set", "Remove reminder": "Remove reminder", "Remove save for later": "Remove save for later", + "Replied to a thread": "Replied to a thread", "Reply": "Reply", "Reply to {{ authorName }}": "Reply to {{ authorName }}", "Reply to Message": "Reply to Message", @@ -377,6 +378,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Upload type: \"{{ type }}\" is not allowed", "User uploaded content": "User uploaded content", "Video": "Video", + "View": "View", "View {{count}} comments_one": "View {{count}} comment", "View {{count}} comments_other": "View {{count}} comments", "View original": "View original", diff --git a/src/i18n/es.json b/src/i18n/es.json index 1e48f15b8..aa887fcc1 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -295,6 +295,7 @@ "Reminder set": "Recordatorio establecido", "Remove reminder": "Eliminar recordatorio", "Remove save for later": "Quitar guardar para después", + "Replied to a thread": "Respondió en un hilo", "Reply": "Responder", "Reply to {{ authorName }}": "Responder a {{ authorName }}", "Reply to Message": "Responder al mensaje", @@ -388,6 +389,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Tipo de carga: \"{{ type }}\" no está permitido", "User uploaded content": "Contenido subido por el usuario", "Video": "Vídeo", + "View": "Ver", "View {{count}} comments_one": "Ver {{count}} comentario", "View {{count}} comments_many": "Ver {{count}} comentarios", "View {{count}} comments_other": "Ver {{count}} comentarios", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 08b04344e..1c485163f 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -295,6 +295,7 @@ "Reminder set": "Rappel défini", "Remove reminder": "Supprimer le rappel", "Remove save for later": "Supprimer « Enregistrer pour plus tard »", + "Replied to a thread": "A répondu à un fil", "Reply": "Répondre", "Reply to {{ authorName }}": "Répondre à {{ authorName }}", "Reply to Message": "Répondre au message", @@ -388,6 +389,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Le type de fichier : \"{{ type }}\" n'est pas autorisé", "User uploaded content": "Contenu téléchargé par l'utilisateur", "Video": "Vidéo", + "View": "Voir", "View {{count}} comments_one": "Voir {{count}} commentaire", "View {{count}} comments_many": "Voir {{count}} commentaires", "View {{count}} comments_other": "Voir {{count}} commentaires", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index 34f7b5e6e..d2e7f0269 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -289,6 +289,7 @@ "Reminder set": "अनुस्मारक सेट किया गया", "Remove reminder": "रिमाइंडर हटाएं", "Remove save for later": "बाद में देखें हटाएं", + "Replied to a thread": "थ्रेड में जवाब दिया", "Reply": "जवाब दे दो", "Reply to {{ authorName }}": "{{ authorName }} को जवाब दें", "Reply to Message": "संदेश का जवाब दें", @@ -378,6 +379,7 @@ "Upload type: \"{{ type }}\" is not allowed": "अपलोड प्रकार: \"{{ type }}\" की अनुमति नहीं है", "User uploaded content": "उपयोगकर्ता अपलोड की गई सामग्री", "Video": "वीडियो", + "View": "देखें", "View {{count}} comments_one": "देखें {{count}} टिप्पणी", "View {{count}} comments_other": "देखें {{count}} टिप्पणियाँ", "View original": "मूल देखें", diff --git a/src/i18n/it.json b/src/i18n/it.json index 7e358f5c6..96a0aa4dd 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -295,6 +295,7 @@ "Reminder set": "Promemoria impostato", "Remove reminder": "Rimuovi promemoria", "Remove save for later": "Rimuovi Salva per dopo", + "Replied to a thread": "Ha risposto in un thread", "Reply": "Rispondi", "Reply to {{ authorName }}": "Rispondi a {{ authorName }}", "Reply to Message": "Rispondi al messaggio", @@ -388,6 +389,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Tipo di caricamento: \"{{ type }}\" non è consentito", "User uploaded content": "Contenuto caricato dall'utente", "Video": "Video", + "View": "Visualizza", "View {{count}} comments_one": "Visualizza {{count}} commento", "View {{count}} comments_many": "Visualizza {{count}} commenti", "View {{count}} comments_other": "Visualizza {{count}} commenti", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index b3d7ff772..9e3ee9156 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -287,6 +287,7 @@ "Reminder set": "リマインダーを設定しました", "Remove reminder": "リマインダーを削除", "Remove save for later": "「後で見る」を削除", + "Replied to a thread": "スレッドに返信しました", "Reply": "返事", "Reply to {{ authorName }}": "{{ authorName }} に返信", "Reply to Message": "メッセージに返信", @@ -376,6 +377,7 @@ "Upload type: \"{{ type }}\" is not allowed": "アップロードタイプ:\"{{ type }}\"は許可されていません", "User uploaded content": "ユーザーがアップロードしたコンテンツ", "Video": "動画", + "View": "表示", "View {{count}} comments_one": "{{count}} コメントを表示", "View {{count}} comments_other": "{{count}} コメントを表示", "View original": "原文を表示", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index ec521d50c..3b41ad64d 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -287,6 +287,7 @@ "Reminder set": "알림 설정됨", "Remove reminder": "알림 제거", "Remove save for later": "나중에 보기 제거", + "Replied to a thread": "스레드에 답글을 남겼습니다", "Reply": "답장", "Reply to {{ authorName }}": "{{ authorName }}님에게 답장", "Reply to Message": "메시지에 답장", @@ -376,6 +377,7 @@ "Upload type: \"{{ type }}\" is not allowed": "업로드 유형: \"{{ type }}\"은(는) 허용되지 않습니다.", "User uploaded content": "사용자 업로드 콘텐츠", "Video": "동영상", + "View": "보기", "View {{count}} comments_one": "{{count}}개의 댓글 보기", "View {{count}} comments_other": "{{count}}개의 댓글 보기", "View original": "원문 보기", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index ed74026ad..f0d6a9c2e 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -288,6 +288,7 @@ "Reminder set": "Herinnering ingesteld", "Remove reminder": "Herinnering verwijderen", "Remove save for later": "Verwijder 'Bewaren voor later'", + "Replied to a thread": "Heeft gereageerd in een thread", "Reply": "Antwoord", "Reply to {{ authorName }}": "Antwoord aan {{ authorName }}", "Reply to Message": "Antwoord op bericht", @@ -379,6 +380,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Uploadtype: \"{{ type }}\" is niet toegestaan", "User uploaded content": "Gebruikersgeüploade inhoud", "Video": "Video", + "View": "Bekijken", "View {{count}} comments_one": "Bekijk {{count}} opmerkingen", "View {{count}} comments_other": "Bekijk {{count}} opmerkingen", "View original": "Origineel bekijken", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 4bb5d7b7e..98470db28 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -295,6 +295,7 @@ "Reminder set": "Lembrete definido", "Remove reminder": "Remover lembrete", "Remove save for later": "Remover Salvar para depois", + "Replied to a thread": "Respondeu em um tópico", "Reply": "Responder", "Reply to {{ authorName }}": "Responder a {{ authorName }}", "Reply to Message": "Responder à mensagem", @@ -388,6 +389,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Tipo de upload: \"{{ type }}\" não é permitido", "User uploaded content": "Conteúdo enviado pelo usuário", "Video": "Vídeo", + "View": "Ver", "View {{count}} comments_one": "Ver {{count}} comentário", "View {{count}} comments_many": "Ver {{count}} comentários", "View {{count}} comments_other": "Ver {{count}} comentários", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index e92efa8d5..98333a7d3 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -302,6 +302,7 @@ "Reminder set": "Напоминание установлено", "Remove reminder": "Удалить напоминание", "Remove save for later": "Удалить «Сохранить на потом»", + "Replied to a thread": "Ответил в ветке", "Reply": "Ответить", "Reply to {{ authorName }}": "Ответить {{ authorName }}", "Reply to Message": "Ответить на сообщение", @@ -399,6 +400,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Тип загрузки: \"{{ type }}\" не разрешен", "User uploaded content": "Пользователь загрузил контент", "Video": "Видео", + "View": "Просмотр", "View {{count}} comments_one": "Просмотреть {{count}} комментарий", "View {{count}} comments_few": "Просмотреть {{count}} комментариев", "View {{count}} comments_many": "Просмотреть {{count}} комментариев", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index 007bc03a4..6928cf793 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -288,6 +288,7 @@ "Reminder set": "Hatırlatıcı ayarlandı", "Remove reminder": "Hatırlatıcıyı kaldır", "Remove save for later": "Sonraya kaydet'i kaldır", + "Replied to a thread": "Bir iş parçacığına yanıt verdi", "Reply": "Cevapla", "Reply to {{ authorName }}": "{{ authorName }} kişisine yanıt ver", "Reply to Message": "Mesaja Cevapla", @@ -377,6 +378,7 @@ "Upload type: \"{{ type }}\" is not allowed": "Yükleme türü: \"{{ type }}\" izin verilmez", "User uploaded content": "Kullanıcı tarafından yüklenen içerik", "Video": "Video", + "View": "Görüntüle", "View {{count}} comments_one": "{{count}} yorumu görüntüle", "View {{count}} comments_other": "{{count}} yorumu görüntüle", "View original": "Orijinali görüntüle",