stream-chat-react 12.11.1 → 12.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +41 -26
- package/dist/components/Channel/channelState.d.ts +1 -1
- package/dist/components/Channel/channelState.js +2 -1
- package/dist/components/ChannelList/ChannelList.js +15 -9
- package/dist/components/ChannelPreview/ChannelPreview.d.ts +2 -2
- package/dist/components/ChannelPreview/ChannelPreview.js +3 -4
- package/dist/components/ChannelPreview/ChannelPreviewMessenger.d.ts +1 -1
- package/dist/components/ChannelSearch/SearchBar.js +7 -8
- package/dist/components/ChannelSearch/SearchResults.js +8 -7
- package/dist/components/Chat/Chat.d.ts +5 -2
- package/dist/components/Chat/Chat.js +12 -2
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Chat/hooks/useCreateChatContext.js +3 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.d.ts +1 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +1 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.d.ts +1 -0
- package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.js +8 -2
- package/dist/components/MediaRecorder/classes/MediaRecorderController.d.ts +6 -1
- package/dist/components/MediaRecorder/classes/MediaRecorderController.js +6 -8
- package/dist/components/Message/renderText/renderText.d.ts +3 -3
- package/dist/components/Message/renderText/renderText.js +3 -3
- package/dist/context/ChatContext.d.ts +3 -1
- package/dist/context/ComponentContext.d.ts +23 -0
- package/dist/experimental/Search/Search.d.ts +12 -0
- package/dist/experimental/Search/Search.js +25 -0
- package/dist/experimental/Search/SearchBar/SearchBar.d.ts +2 -0
- package/dist/experimental/Search/SearchBar/SearchBar.js +56 -0
- package/dist/experimental/Search/SearchBar/index.d.ts +1 -0
- package/dist/experimental/Search/SearchBar/index.js +1 -0
- package/dist/experimental/Search/SearchContext.d.ts +23 -0
- package/dist/experimental/Search/SearchContext.js +10 -0
- package/dist/experimental/Search/SearchResults/SearchResultItem.d.ts +19 -0
- package/dist/experimental/Search/SearchResults/SearchResultItem.js +62 -0
- package/dist/experimental/Search/SearchResults/SearchResults.d.ts +3 -0
- package/dist/experimental/Search/SearchResults/SearchResults.js +21 -0
- package/dist/experimental/Search/SearchResults/SearchResultsHeader.d.ts +3 -0
- package/dist/experimental/Search/SearchResults/SearchResultsHeader.js +31 -0
- package/dist/experimental/Search/SearchResults/SearchResultsPresearch.d.ts +6 -0
- package/dist/experimental/Search/SearchResults/SearchResultsPresearch.js +6 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultList.d.ts +9 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultList.js +22 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultListFooter.d.ts +3 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultListFooter.js +16 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResults.d.ts +7 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResults.js +21 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsEmpty.d.ts +2 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsEmpty.js +6 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsHeader.d.ts +1 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsHeader.js +1 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsLoadingIndicator.d.ts +2 -0
- package/dist/experimental/Search/SearchResults/SearchSourceResultsLoadingIndicator.js +8 -0
- package/dist/experimental/Search/SearchResults/index.d.ts +9 -0
- package/dist/experimental/Search/SearchResults/index.js +9 -0
- package/dist/experimental/Search/SearchSourceResultsContext.d.ts +13 -0
- package/dist/experimental/Search/SearchSourceResultsContext.js +10 -0
- package/dist/experimental/Search/hooks/index.d.ts +2 -0
- package/dist/experimental/Search/hooks/index.js +2 -0
- package/dist/experimental/Search/hooks/useSearchFocusedMessage.d.ts +2 -0
- package/dist/experimental/Search/hooks/useSearchFocusedMessage.js +8 -0
- package/dist/experimental/Search/hooks/useSearchQueriesInProgress.d.ts +6 -0
- package/dist/experimental/Search/hooks/useSearchQueriesInProgress.js +22 -0
- package/dist/experimental/Search/index.d.ts +5 -0
- package/dist/experimental/Search/index.js +5 -0
- package/dist/experimental/index.browser.cjs +11286 -301
- package/dist/experimental/index.browser.cjs.map +4 -4
- package/dist/experimental/index.d.ts +1 -0
- package/dist/experimental/index.js +1 -0
- package/dist/experimental/index.node.cjs +13176 -301
- package/dist/experimental/index.node.cjs.map +4 -4
- package/dist/i18n/Streami18n.d.ts +7 -0
- package/dist/i18n/de.json +7 -0
- package/dist/i18n/en.json +7 -0
- package/dist/i18n/es.json +7 -0
- package/dist/i18n/fr.json +8 -1
- package/dist/i18n/hi.json +7 -0
- package/dist/i18n/it.json +7 -0
- package/dist/i18n/ja.json +7 -0
- package/dist/i18n/ko.json +7 -0
- package/dist/i18n/nl.json +7 -0
- package/dist/i18n/pt.json +8 -1
- package/dist/i18n/ru.json +7 -0
- package/dist/i18n/tr.json +7 -0
- package/dist/index.browser.cjs +15820 -14852
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +17734 -16888
- package/dist/index.node.cjs.map +4 -4
- package/package.json +6 -7
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
|
2
|
-
import { ChannelQueryOptions, EventAPIResponse, Message, MessageResponse, Channel as StreamChannel, StreamChat, UpdatedMessage } from 'stream-chat';
|
|
3
2
|
import { OnMentionAction } from './hooks/useMentionsHandlers';
|
|
4
3
|
import { LoadingErrorIndicatorProps } from '../Loading';
|
|
5
4
|
import { ComponentContextValue, StreamMessage } from '../../context';
|
|
5
|
+
import type { ChannelQueryOptions, EventAPIResponse, Message, MessageResponse, Channel as StreamChannel, StreamChat, UpdatedMessage } from 'stream-chat';
|
|
6
6
|
import type { MessageInputProps } from '../MessageInput';
|
|
7
7
|
import type { ChannelUnreadUiState, CustomTrigger, DefaultStreamChatGenerics, GiphyVersions, ImageAttachmentSizeHandler, SendMessageOptions, UpdateMessageOptions, VideoAttachmentSizeHandler } from '../../types/types';
|
|
8
8
|
import type { URLEnrichmentConfig } from '../MessageInput/hooks/useLinkPreviews';
|
|
@@ -14,14 +14,15 @@ import { LoadingErrorIndicator as DefaultLoadingErrorIndicator, } from '../Loadi
|
|
|
14
14
|
import { LoadingChannel as DefaultLoadingIndicator } from './LoadingChannel';
|
|
15
15
|
import { DropzoneProvider } from '../MessageInput/DropzoneProvider';
|
|
16
16
|
import { ChannelActionProvider, ChannelStateProvider, TypingProvider, useChatContext, useTranslationContext, WithComponents, } from '../../context';
|
|
17
|
+
import { CHANNEL_CONTAINER_ID } from './constants';
|
|
17
18
|
import { DEFAULT_HIGHLIGHT_DURATION, DEFAULT_INITIAL_CHANNEL_PAGE_SIZE, DEFAULT_JUMP_TO_PAGE_SIZE, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, DEFAULT_THREAD_PAGE_SIZE, } from '../../constants/limits';
|
|
18
19
|
import { hasMoreMessagesProbably } from '../MessageList';
|
|
19
20
|
import { getChatContainerClass, useChannelContainerClasses, useImageFlagEmojisOnWindowsClass, } from './hooks/useChannelContainerClasses';
|
|
20
21
|
import { findInMsgSetByDate, findInMsgSetById, makeAddNotifications } from './utils';
|
|
22
|
+
import { useThreadContext } from '../Threads';
|
|
21
23
|
import { getChannel } from '../../utils';
|
|
22
24
|
import { getImageAttachmentConfiguration, getVideoAttachmentConfiguration, } from '../Attachment/attachment-sizing';
|
|
23
|
-
import {
|
|
24
|
-
import { CHANNEL_CONTAINER_ID } from './constants';
|
|
25
|
+
import { useSearchFocusedMessage } from '../../experimental/Search/hooks';
|
|
25
26
|
const isUserResponseArray = (output) => output[0]?.id != null;
|
|
26
27
|
const ChannelContainer = ({ children, className: additionalClassName, ...props }) => {
|
|
27
28
|
const { customClasses, theme } = useChatContext('Channel');
|
|
@@ -53,7 +54,7 @@ const ChannelInner = (props) => {
|
|
|
53
54
|
const channelQueryOptions = useMemo(() => defaultsDeep(propChannelQueryOptions, {
|
|
54
55
|
messages: { limit: DEFAULT_INITIAL_CHANNEL_PAGE_SIZE },
|
|
55
56
|
}), [propChannelQueryOptions]);
|
|
56
|
-
const { client, customClasses, latestMessageDatesByChannels, mutes } = useChatContext('Channel');
|
|
57
|
+
const { client, customClasses, latestMessageDatesByChannels, mutes, searchController } = useChatContext('Channel');
|
|
57
58
|
const { t } = useTranslationContext('Channel');
|
|
58
59
|
const chatContainerClass = getChatContainerClass(customClasses?.chatContainer);
|
|
59
60
|
const windowsEmojiClass = useImageFlagEmojisOnWindowsClass();
|
|
@@ -72,10 +73,12 @@ const ChannelInner = (props) => {
|
|
|
72
73
|
hasMore: channel.state.messagePagination.hasPrev,
|
|
73
74
|
loading: !channel.initialized,
|
|
74
75
|
});
|
|
76
|
+
const jumpToMessageFromSearch = useSearchFocusedMessage();
|
|
75
77
|
const isMounted = useIsMounted();
|
|
76
78
|
const originalTitle = useRef('');
|
|
77
79
|
const lastRead = useRef(undefined);
|
|
78
80
|
const online = useRef(true);
|
|
81
|
+
const clearHighlightedMessageTimeoutId = useRef(null);
|
|
79
82
|
const channelCapabilitiesArray = channel.data?.own_capabilities;
|
|
80
83
|
const throttledCopyStateFromChannel = throttle(() => dispatch({ channel, type: 'copyStateFromChannelOnEvent' }), 500, {
|
|
81
84
|
leading: true,
|
|
@@ -282,6 +285,28 @@ const ChannelInner = (props) => {
|
|
|
282
285
|
if (message)
|
|
283
286
|
dispatch({ message, type: 'setThread' });
|
|
284
287
|
}, [state.messages, state.thread]);
|
|
288
|
+
const handleHighlightedMessageChange = useCallback(({ highlightDuration, highlightedMessageId, }) => {
|
|
289
|
+
dispatch({
|
|
290
|
+
channel,
|
|
291
|
+
highlightedMessageId,
|
|
292
|
+
type: 'jumpToMessageFinished',
|
|
293
|
+
});
|
|
294
|
+
if (clearHighlightedMessageTimeoutId.current) {
|
|
295
|
+
clearTimeout(clearHighlightedMessageTimeoutId.current);
|
|
296
|
+
}
|
|
297
|
+
clearHighlightedMessageTimeoutId.current = setTimeout(() => {
|
|
298
|
+
if (searchController._internalState.getLatestValue().focusedMessage) {
|
|
299
|
+
searchController._internalState.partialNext({ focusedMessage: undefined });
|
|
300
|
+
}
|
|
301
|
+
clearHighlightedMessageTimeoutId.current = null;
|
|
302
|
+
dispatch({ type: 'clearHighlightedMessage' });
|
|
303
|
+
}, highlightDuration ?? DEFAULT_HIGHLIGHT_DURATION);
|
|
304
|
+
}, [channel, searchController]);
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
if (!jumpToMessageFromSearch?.id)
|
|
307
|
+
return;
|
|
308
|
+
handleHighlightedMessageChange({ highlightedMessageId: jumpToMessageFromSearch.id });
|
|
309
|
+
}, [jumpToMessageFromSearch, handleHighlightedMessageChange]);
|
|
285
310
|
/** MESSAGE */
|
|
286
311
|
// Adds a temporary notification to message list, will be removed after 5 seconds
|
|
287
312
|
const addNotification = useMemo(() => makeAddNotifications(setNotifications, notificationTimeouts.current), []);
|
|
@@ -351,24 +376,15 @@ const ChannelInner = (props) => {
|
|
|
351
376
|
});
|
|
352
377
|
return queryResponse.messages.length;
|
|
353
378
|
};
|
|
354
|
-
const clearHighlightedMessageTimeoutId = useRef(null);
|
|
355
379
|
const jumpToMessage = useCallback(async (messageId, messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE, highlightDuration = DEFAULT_HIGHLIGHT_DURATION) => {
|
|
356
380
|
dispatch({ loadingMore: true, type: 'setLoadingMore' });
|
|
357
381
|
await channel.state.loadMessageIntoState(messageId, undefined, messageLimit);
|
|
358
382
|
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
|
|
359
|
-
|
|
360
|
-
|
|
383
|
+
handleHighlightedMessageChange({
|
|
384
|
+
highlightDuration,
|
|
361
385
|
highlightedMessageId: messageId,
|
|
362
|
-
type: 'jumpToMessageFinished',
|
|
363
386
|
});
|
|
364
|
-
|
|
365
|
-
clearTimeout(clearHighlightedMessageTimeoutId.current);
|
|
366
|
-
}
|
|
367
|
-
clearHighlightedMessageTimeoutId.current = setTimeout(() => {
|
|
368
|
-
clearHighlightedMessageTimeoutId.current = null;
|
|
369
|
-
dispatch({ type: 'clearHighlightedMessage' });
|
|
370
|
-
}, highlightDuration);
|
|
371
|
-
}, [channel, loadMoreFinished]);
|
|
387
|
+
}, [channel, handleHighlightedMessageChange, loadMoreFinished]);
|
|
372
388
|
const jumpToLatestMessage = useCallback(async () => {
|
|
373
389
|
await channel.state.loadMessageIntoState('latest');
|
|
374
390
|
loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
|
|
@@ -468,19 +484,18 @@ const ChannelInner = (props) => {
|
|
|
468
484
|
first_unread_message_id: firstUnreadMessageId,
|
|
469
485
|
last_read_message_id: lastReadMessageId,
|
|
470
486
|
});
|
|
471
|
-
|
|
472
|
-
|
|
487
|
+
handleHighlightedMessageChange({
|
|
488
|
+
highlightDuration,
|
|
473
489
|
highlightedMessageId: firstUnreadMessageId,
|
|
474
|
-
type: 'jumpToMessageFinished',
|
|
475
490
|
});
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
491
|
+
}, [
|
|
492
|
+
addNotification,
|
|
493
|
+
channel,
|
|
494
|
+
handleHighlightedMessageChange,
|
|
495
|
+
loadMoreFinished,
|
|
496
|
+
t,
|
|
497
|
+
channelUnreadUiState,
|
|
498
|
+
]);
|
|
484
499
|
const deleteMessage = useCallback(async (message) => {
|
|
485
500
|
if (!message?.id) {
|
|
486
501
|
throw new Error('Cannot delete a message - missing message ID.');
|
|
@@ -13,7 +13,7 @@ export type ChannelStateReducerAction<StreamChatGenerics extends DefaultStreamCh
|
|
|
13
13
|
channel: Channel<StreamChatGenerics>;
|
|
14
14
|
type: 'copyStateFromChannelOnEvent';
|
|
15
15
|
} | {
|
|
16
|
-
|
|
16
|
+
channel: Channel<StreamChatGenerics>;
|
|
17
17
|
highlightedMessageId: string;
|
|
18
18
|
type: 'jumpToMessageFinished';
|
|
19
19
|
} | {
|
|
@@ -59,8 +59,9 @@ export const makeChannelReducer = () => (state, action) => {
|
|
|
59
59
|
case 'jumpToMessageFinished': {
|
|
60
60
|
return {
|
|
61
61
|
...state,
|
|
62
|
-
hasMoreNewer: action.
|
|
62
|
+
hasMoreNewer: action.channel.state.messagePagination.hasNext,
|
|
63
63
|
highlightedMessageId: action.highlightedMessageId,
|
|
64
|
+
messages: action.channel.state.messages,
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
case 'clearHighlightedMessage': {
|
|
@@ -4,6 +4,7 @@ import { ChannelListMessenger } from './ChannelListMessenger';
|
|
|
4
4
|
import { useConnectionRecoveredListener } from './hooks/useConnectionRecoveredListener';
|
|
5
5
|
import { useMobileNavigation } from './hooks/useMobileNavigation';
|
|
6
6
|
import { usePaginatedChannels, } from './hooks/usePaginatedChannels';
|
|
7
|
+
import { useChannelListShape, usePrepareShapeHandlers, } from './hooks/useChannelListShape';
|
|
7
8
|
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUpwards } from './utils';
|
|
8
9
|
import { Avatar as DefaultAvatar } from '../Avatar';
|
|
9
10
|
import { ChannelPreview, } from '../ChannelPreview/ChannelPreview';
|
|
@@ -12,18 +13,24 @@ import { EmptyStateIndicator as DefaultEmptyStateIndicator, } from '../EmptyStat
|
|
|
12
13
|
import { LoadingChannels } from '../Loading/LoadingChannels';
|
|
13
14
|
import { LoadMorePaginator } from '../LoadMore/LoadMorePaginator';
|
|
14
15
|
import { NullComponent } from '../UtilityComponents';
|
|
15
|
-
import { ChannelListContextProvider } from '../../context';
|
|
16
|
+
import { ChannelListContextProvider, useComponentContext, } from '../../context';
|
|
16
17
|
import { useChatContext } from '../../context/ChatContext';
|
|
17
|
-
import {
|
|
18
|
+
import { useStateStore } from '../../store';
|
|
18
19
|
const DEFAULT_FILTERS = {};
|
|
19
20
|
const DEFAULT_OPTIONS = {};
|
|
20
21
|
const DEFAULT_SORT = {};
|
|
22
|
+
const searchControllerStateSelector = (nextValue) => ({
|
|
23
|
+
searchIsActive: nextValue.isActive,
|
|
24
|
+
});
|
|
21
25
|
const UnMemoizedChannelList = (props) => {
|
|
22
26
|
const { additionalChannelSearchProps, allowNewMessagesFromUnfilteredChannels = true, Avatar = DefaultAvatar, channelRenderFilterFn, ChannelSearch = DefaultChannelSearch, customActiveChannel, customQueryChannels, EmptyStateIndicator = DefaultEmptyStateIndicator, filters = {}, getLatestMessagePreview, List = ChannelListMessenger, LoadingErrorIndicator = NullComponent, LoadingIndicator = LoadingChannels, lockChannelOrder = false, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, options, Paginator = LoadMorePaginator, Preview, recoveryThrottleIntervalMs, renderChannels, sendChannelsToList = false, setActiveChannelOnMount = true, showChannelSearch = false, sort = DEFAULT_SORT, watchers = {}, } = props;
|
|
23
|
-
const { channel, channelsQueryState, client, closeMobileNav, customClasses, navOpen = false, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = useChatContext('ChannelList');
|
|
27
|
+
const { channel, channelsQueryState, client, closeMobileNav, customClasses, navOpen = false, searchController, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = useChatContext('ChannelList');
|
|
28
|
+
const { Search } = useComponentContext(); // FIXME: us component context to retrieve ChannelPreview UI components too
|
|
24
29
|
const channelListRef = useRef(null);
|
|
25
30
|
const [channelUpdateCount, setChannelUpdateCount] = useState(0);
|
|
26
31
|
const [searchActive, setSearchActive] = useState(false);
|
|
32
|
+
// Indicator relevant when Search component that relies on SearchController is used
|
|
33
|
+
const { searchIsActive } = useStateStore(searchController.state, searchControllerStateSelector);
|
|
27
34
|
/**
|
|
28
35
|
* Set a channel with id {customActiveChannel} as active and move it to the top of the list.
|
|
29
36
|
* If customActiveChannel prop is absent, then set the first channel in list as active channel.
|
|
@@ -65,13 +72,11 @@ const UnMemoizedChannelList = (props) => {
|
|
|
65
72
|
const onSearch = useCallback((event) => {
|
|
66
73
|
setSearchActive(!!event.target.value);
|
|
67
74
|
additionalChannelSearchProps?.onSearch?.(event);
|
|
68
|
-
|
|
69
|
-
}, []);
|
|
75
|
+
}, [additionalChannelSearchProps]);
|
|
70
76
|
const onSearchExit = useCallback(() => {
|
|
71
77
|
setSearchActive(false);
|
|
72
78
|
additionalChannelSearchProps?.onSearchExit?.();
|
|
73
|
-
|
|
74
|
-
}, []);
|
|
79
|
+
}, [additionalChannelSearchProps]);
|
|
75
80
|
const { channels, hasNextPage, loadNextPage, setChannels } = usePaginatedChannels(client, filters || DEFAULT_FILTERS, sort || DEFAULT_SORT, options || DEFAULT_OPTIONS, activeChannelHandler, recoveryThrottleIntervalMs, customQueryChannels);
|
|
76
81
|
const loadedChannels = channelRenderFilterFn
|
|
77
82
|
? channelRenderFilterFn(channels)
|
|
@@ -132,10 +137,11 @@ const UnMemoizedChannelList = (props) => {
|
|
|
132
137
|
'str-chat--windows-flags': useImageFlagEmojisOnWindows && navigator.userAgent.match(/Win/),
|
|
133
138
|
[`${baseClass}--open`]: navOpen,
|
|
134
139
|
});
|
|
135
|
-
const showChannelList = !searchActive || additionalChannelSearchProps?.popupResults;
|
|
140
|
+
const showChannelList = (!searchActive && !searchIsActive) || additionalChannelSearchProps?.popupResults;
|
|
136
141
|
return (React.createElement(ChannelListContextProvider, { value: { channels, setChannels } },
|
|
137
142
|
React.createElement("div", { className: className, ref: channelListRef },
|
|
138
|
-
showChannelSearch &&
|
|
143
|
+
showChannelSearch &&
|
|
144
|
+
(Search ? (React.createElement(Search, { directMessagingChannelType: additionalChannelSearchProps?.channelType, disabled: additionalChannelSearchProps?.disabled, exitSearchOnInputBlur: additionalChannelSearchProps?.clearSearchOnClickOutside, placeholder: additionalChannelSearchProps?.placeholder })) : (React.createElement(ChannelSearch, { onSearch: onSearch, onSearchExit: onSearchExit, setChannels: setChannels, ...additionalChannelSearchProps }))),
|
|
139
145
|
showChannelList && (React.createElement(List, { error: channelsQueryState.error, loadedChannels: sendChannelsToList ? loadedChannels : undefined, loading: !!channelsQueryState.queryInProgress &&
|
|
140
146
|
['reload', 'uninitialized'].includes(channelsQueryState.queryInProgress), LoadingErrorIndicator: LoadingErrorIndicator, LoadingIndicator: LoadingIndicator, setChannels: setChannels }, !loadedChannels?.length ? (React.createElement(EmptyStateIndicator, { listType: 'channel' })) : (React.createElement(Paginator, { hasNextPage: hasNextPage, isLoading: channelsQueryState.queryInProgress === 'load-more', loadNextPage: loadNextPage }, renderChannels
|
|
141
147
|
? renderChannels(loadedChannels, renderChannel)
|
|
@@ -8,8 +8,6 @@ import type { StreamMessage } from '../../context/ChannelStateContext';
|
|
|
8
8
|
import type { TranslationContextValue } from '../../context/TranslationContext';
|
|
9
9
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
10
10
|
export type ChannelPreviewUIComponentProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = ChannelPreviewProps<StreamChatGenerics> & {
|
|
11
|
-
/** If the component's channel is the active (selected) Channel */
|
|
12
|
-
active?: boolean;
|
|
13
11
|
/** Image of Channel to display */
|
|
14
12
|
displayImage?: string;
|
|
15
13
|
/** Title of Channel to display */
|
|
@@ -30,6 +28,8 @@ export type ChannelPreviewUIComponentProps<StreamChatGenerics extends DefaultStr
|
|
|
30
28
|
export type ChannelPreviewProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
31
29
|
/** Comes from either the `channelRenderFilterFn` or `usePaginatedChannels` call from [ChannelList](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelList.tsx) */
|
|
32
30
|
channel: Channel<StreamChatGenerics>;
|
|
31
|
+
/** If the component's channel is the active (selected) Channel */
|
|
32
|
+
active?: boolean;
|
|
33
33
|
/** Current selected channel object */
|
|
34
34
|
activeChannel?: Channel<StreamChatGenerics>;
|
|
35
35
|
/** UI component to display an avatar, defaults to [Avatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/Avatar.tsx) component and accepts the same props as: [ChannelAvatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/ChannelAvatar.tsx) */
|
|
@@ -8,7 +8,7 @@ import { useChatContext } from '../../context/ChatContext';
|
|
|
8
8
|
import { useTranslationContext } from '../../context/TranslationContext';
|
|
9
9
|
import { useMessageDeliveryStatus, } from './hooks/useMessageDeliveryStatus';
|
|
10
10
|
export const ChannelPreview = (props) => {
|
|
11
|
-
const { channel, channelUpdateCount, getLatestMessagePreview = defaultGetLatestMessagePreview, Preview = ChannelPreviewMessenger, } = props;
|
|
11
|
+
const { active, channel, channelUpdateCount, getLatestMessagePreview = defaultGetLatestMessagePreview, Preview = ChannelPreviewMessenger, } = props;
|
|
12
12
|
const { channel: activeChannel, client, isMessageAIGenerated, setActiveChannel, } = useChatContext('ChannelPreview');
|
|
13
13
|
const { t, userLanguage } = useTranslationContext('ChannelPreview');
|
|
14
14
|
const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({
|
|
@@ -20,7 +20,7 @@ export const ChannelPreview = (props) => {
|
|
|
20
20
|
channel,
|
|
21
21
|
lastMessage,
|
|
22
22
|
});
|
|
23
|
-
const isActive = activeChannel?.cid === channel.cid;
|
|
23
|
+
const isActive = typeof active === 'undefined' ? activeChannel?.cid === channel.cid : active;
|
|
24
24
|
const { muted } = useIsChannelMuted(channel);
|
|
25
25
|
useEffect(() => {
|
|
26
26
|
const handleEvent = (event) => {
|
|
@@ -31,8 +31,7 @@ export const ChannelPreview = (props) => {
|
|
|
31
31
|
};
|
|
32
32
|
client.on('notification.mark_read', handleEvent);
|
|
33
33
|
return () => client.off('notification.mark_read', handleEvent);
|
|
34
|
-
|
|
35
|
-
}, []);
|
|
34
|
+
}, [channel, client]);
|
|
36
35
|
useEffect(() => {
|
|
37
36
|
const handleEvent = (event) => {
|
|
38
37
|
if (channel.cid !== event.cid)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
3
2
|
import type { ChannelPreviewUIComponentProps } from './ChannelPreview';
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
4
4
|
/**
|
|
5
5
|
* Used as preview component for channel item in [ChannelList](#channellist) component.
|
|
6
6
|
* Its best suited for messenger type chat.
|
|
@@ -33,25 +33,24 @@ export const SearchBar = (props) => {
|
|
|
33
33
|
useEffect(() => {
|
|
34
34
|
if (!props.inputRef.current)
|
|
35
35
|
return;
|
|
36
|
+
const input = props.inputRef.current;
|
|
36
37
|
const handleFocus = () => {
|
|
37
38
|
activateSearch();
|
|
38
39
|
};
|
|
39
40
|
const handleBlur = (e) => {
|
|
40
41
|
e.stopPropagation(); // handle blur/focus state with React state
|
|
41
42
|
};
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
input.addEventListener('focus', handleFocus);
|
|
44
|
+
input.addEventListener('blur', handleBlur);
|
|
44
45
|
return () => {
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
input.removeEventListener('focus', handleFocus);
|
|
47
|
+
input.removeEventListener('blur', handleBlur);
|
|
47
48
|
};
|
|
48
|
-
|
|
49
|
-
}, []);
|
|
49
|
+
}, [activateSearch, props.inputRef]);
|
|
50
50
|
const handleClearClick = useCallback(() => {
|
|
51
51
|
exitSearch();
|
|
52
52
|
inputProps.inputRef.current?.focus();
|
|
53
|
-
|
|
54
|
-
}, []);
|
|
53
|
+
}, [exitSearch, inputProps.inputRef]);
|
|
55
54
|
const closeAppMenu = useCallback(() => setMenuIsOpen(false), []);
|
|
56
55
|
return (React.createElement("div", { className: 'str-chat__channel-search-bar', "data-testid": 'search-bar', ref: searchBarRef },
|
|
57
56
|
inputIsFocused ? (React.createElement(SearchBarButton, { className: 'str-chat__channel-search-bar-button--exit-search', onClick: exitSearch },
|
|
@@ -60,14 +60,15 @@ export const SearchResults = (props) => {
|
|
|
60
60
|
}
|
|
61
61
|
if (event.key === 'Enter') {
|
|
62
62
|
event.preventDefault();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
setFocusedResult((prevFocused) => {
|
|
64
|
+
if (typeof prevFocused !== 'undefined') {
|
|
65
|
+
selectResult(results[prevFocused]);
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return prevFocused;
|
|
69
|
+
});
|
|
67
70
|
}
|
|
68
|
-
},
|
|
69
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
-
[focusedResult]);
|
|
71
|
+
}, [results, selectResult]);
|
|
71
72
|
useEffect(() => {
|
|
72
73
|
document.addEventListener('keydown', handleKeyDown, false);
|
|
73
74
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
|
2
|
-
import { CustomClasses } from '../../context/ChatContext';
|
|
3
2
|
import type { StreamChat } from 'stream-chat';
|
|
3
|
+
import { SearchController } from 'stream-chat';
|
|
4
|
+
import { CustomClasses } from '../../context/ChatContext';
|
|
5
|
+
import type { MessageContextValue } from '../../context';
|
|
4
6
|
import type { SupportedTranslations } from '../../i18n/types';
|
|
5
7
|
import type { Streami18n } from '../../i18n/Streami18n';
|
|
6
8
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
7
|
-
import type { MessageContextValue } from '../../context';
|
|
8
9
|
export type ChatProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
9
10
|
/** The StreamChat client object */
|
|
10
11
|
client: StreamChat<StreamChatGenerics>;
|
|
@@ -16,6 +17,8 @@ export type ChatProps<StreamChatGenerics extends DefaultStreamChatGenerics = Def
|
|
|
16
17
|
i18nInstance?: Streami18n;
|
|
17
18
|
/** Initial status of mobile navigation */
|
|
18
19
|
initialNavOpen?: boolean;
|
|
20
|
+
/** Instance of SearchController class that allows to control all the search operations. */
|
|
21
|
+
searchController?: SearchController<StreamChatGenerics>;
|
|
19
22
|
/** Used for injecting className/s to the Channel and ChannelList components */
|
|
20
23
|
theme?: string;
|
|
21
24
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { ChannelSearchSource, MessageSearchSource, SearchController, UserSearchSource, } from 'stream-chat';
|
|
2
3
|
import { useChat } from './hooks/useChat';
|
|
3
4
|
import { useCreateChatContext } from './hooks/useCreateChatContext';
|
|
4
5
|
import { useChannelsQueryState } from './hooks/useChannelsQueryState';
|
|
@@ -9,9 +10,17 @@ import { TranslationProvider } from '../../context/TranslationContext';
|
|
|
9
10
|
* as it provides the ChatContext.
|
|
10
11
|
*/
|
|
11
12
|
export const Chat = (props) => {
|
|
12
|
-
const { children, client, customClasses, defaultLanguage, i18nInstance, initialNavOpen = true, isMessageAIGenerated, theme = 'messaging light', useImageFlagEmojisOnWindows = false, } = props;
|
|
13
|
+
const { children, client, customClasses, defaultLanguage, i18nInstance, initialNavOpen = true, isMessageAIGenerated, searchController: customChannelSearchController, theme = 'messaging light', useImageFlagEmojisOnWindows = false, } = props;
|
|
13
14
|
const { channel, closeMobileNav, getAppSettings, latestMessageDatesByChannels, mutes, navOpen, openMobileNav, setActiveChannel, translators, } = useChat({ client, defaultLanguage, i18nInstance, initialNavOpen });
|
|
14
15
|
const channelsQueryState = useChannelsQueryState();
|
|
16
|
+
const searchController = useMemo(() => customChannelSearchController ??
|
|
17
|
+
new SearchController({
|
|
18
|
+
sources: [
|
|
19
|
+
new ChannelSearchSource(client),
|
|
20
|
+
new UserSearchSource(client),
|
|
21
|
+
new MessageSearchSource(client),
|
|
22
|
+
],
|
|
23
|
+
}), [client, customChannelSearchController]);
|
|
15
24
|
const chatContextValue = useCreateChatContext({
|
|
16
25
|
channel,
|
|
17
26
|
channelsQueryState,
|
|
@@ -24,6 +33,7 @@ export const Chat = (props) => {
|
|
|
24
33
|
mutes,
|
|
25
34
|
navOpen,
|
|
26
35
|
openMobileNav,
|
|
36
|
+
searchController,
|
|
27
37
|
setActiveChannel,
|
|
28
38
|
theme,
|
|
29
39
|
useImageFlagEmojisOnWindows,
|
|
@@ -28,7 +28,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
28
28
|
if (!userAgent.includes('stream-chat-react')) {
|
|
29
29
|
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
30
30
|
// the upper-case text between double underscores is replaced with the actual semantic version of the library
|
|
31
|
-
client.setUserAgent(`stream-chat-react-12.
|
|
31
|
+
client.setUserAgent(`stream-chat-react-12.12.0-${userAgent}`);
|
|
32
32
|
}
|
|
33
33
|
client.threads.registerSubscriptions();
|
|
34
34
|
client.polls.registerSubscriptions();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
export const useCreateChatContext = (value) => {
|
|
3
|
-
const { channel, channelsQueryState, client, closeMobileNav, customClasses, getAppSettings, isMessageAIGenerated, latestMessageDatesByChannels, mutes, navOpen, openMobileNav, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = value;
|
|
3
|
+
const { channel, channelsQueryState, client, closeMobileNav, customClasses, getAppSettings, isMessageAIGenerated, latestMessageDatesByChannels, mutes, navOpen, openMobileNav, searchController, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = value;
|
|
4
4
|
const channelCid = channel?.cid;
|
|
5
5
|
const channelsQueryError = channelsQueryState.error;
|
|
6
6
|
const channelsQueryInProgress = channelsQueryState.queryInProgress;
|
|
@@ -19,6 +19,7 @@ export const useCreateChatContext = (value) => {
|
|
|
19
19
|
mutes,
|
|
20
20
|
navOpen,
|
|
21
21
|
openMobileNav,
|
|
22
|
+
searchController,
|
|
22
23
|
setActiveChannel,
|
|
23
24
|
theme,
|
|
24
25
|
useImageFlagEmojisOnWindows,
|
|
@@ -30,6 +31,7 @@ export const useCreateChatContext = (value) => {
|
|
|
30
31
|
channelsQueryInProgress,
|
|
31
32
|
clientValues,
|
|
32
33
|
getAppSettings,
|
|
34
|
+
searchController,
|
|
33
35
|
mutedUsersLength,
|
|
34
36
|
navOpen,
|
|
35
37
|
isMessageAIGenerated,
|
|
@@ -34,7 +34,7 @@ export type InfiniteScrollProps = PaginatorProps & {
|
|
|
34
34
|
/**
|
|
35
35
|
* This component serves a single purpose - load more items on scroll inside the MessageList component
|
|
36
36
|
* It is not a general purpose infinite scroll controller, because:
|
|
37
|
-
* 1. It is re-rendered whenever
|
|
37
|
+
* 1. It is re-rendered whenever queryInProgress, hasNext, hasPrev changes. This can lead to scrollListener to have stale data.
|
|
38
38
|
* 2. It pretends to invoke scrollListener on resize event even though this event is emitted only on window resize. It should
|
|
39
39
|
* rather use ResizeObserver. But then again, it ResizeObserver would invoke a stale version of scrollListener.
|
|
40
40
|
*
|
|
@@ -13,7 +13,7 @@ const mousewheelListener = (event) => {
|
|
|
13
13
|
/**
|
|
14
14
|
* This component serves a single purpose - load more items on scroll inside the MessageList component
|
|
15
15
|
* It is not a general purpose infinite scroll controller, because:
|
|
16
|
-
* 1. It is re-rendered whenever
|
|
16
|
+
* 1. It is re-rendered whenever queryInProgress, hasNext, hasPrev changes. This can lead to scrollListener to have stale data.
|
|
17
17
|
* 2. It pretends to invoke scrollListener on resize event even though this event is emitted only on window resize. It should
|
|
18
18
|
* rather use ResizeObserver. But then again, it ResizeObserver would invoke a stale version of scrollListener.
|
|
19
19
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
|
2
2
|
export type InfiniteScrollPaginatorProps = React.ComponentProps<'div'> & {
|
|
3
3
|
listenToScroll?: (distanceFromBottom: number, distanceFromTop: number, threshold: number) => void;
|
|
4
|
+
loadNextDebounceMs?: number;
|
|
4
5
|
loadNextOnScrollToBottom?: () => void;
|
|
5
6
|
loadNextOnScrollToTop?: () => void;
|
|
6
7
|
/** Offset from when to start the loadNextPage call */
|
|
@@ -12,7 +12,7 @@ const mousewheelListener = (event) => {
|
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
export const InfiniteScrollPaginator = (props) => {
|
|
15
|
-
const { children, className, listenToScroll, loadNextOnScrollToBottom, loadNextOnScrollToTop, threshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, useCapture = false, ...componentProps } = props;
|
|
15
|
+
const { children, className, listenToScroll, loadNextDebounceMs = 500, loadNextOnScrollToBottom, loadNextOnScrollToTop, threshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, useCapture = false, ...componentProps } = props;
|
|
16
16
|
const rootRef = useRef(null);
|
|
17
17
|
const childRef = useRef(null);
|
|
18
18
|
const scrollListener = useMemo(() => debounce(() => {
|
|
@@ -32,7 +32,13 @@ export const InfiniteScrollPaginator = (props) => {
|
|
|
32
32
|
if (distanceFromBottom < Number(threshold)) {
|
|
33
33
|
loadNextOnScrollToBottom?.();
|
|
34
34
|
}
|
|
35
|
-
},
|
|
35
|
+
}, loadNextDebounceMs), [
|
|
36
|
+
listenToScroll,
|
|
37
|
+
loadNextDebounceMs,
|
|
38
|
+
loadNextOnScrollToBottom,
|
|
39
|
+
loadNextOnScrollToTop,
|
|
40
|
+
threshold,
|
|
41
|
+
]);
|
|
36
42
|
useEffect(() => {
|
|
37
43
|
const scrollElement = rootRef.current;
|
|
38
44
|
if (!scrollElement)
|
|
@@ -6,7 +6,12 @@ import { RecordedMediaType } from '../../ReactFileUtilities';
|
|
|
6
6
|
import { TranslationContextValue } from '../../../context';
|
|
7
7
|
import type { LocalVoiceRecordingAttachment } from '../../MessageInput';
|
|
8
8
|
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const RECORDED_MIME_TYPE_BY_BROWSER: {
|
|
10
|
+
readonly audio: {
|
|
11
|
+
readonly others: "audio/webm";
|
|
12
|
+
readonly safari: "audio/mp4;codecs=mp4a.40.2";
|
|
13
|
+
};
|
|
14
|
+
};
|
|
10
15
|
export declare const DEFAULT_AUDIO_TRANSCODER_CONFIG: TranscoderConfig;
|
|
11
16
|
type MediaRecorderConfig = Omit<MediaRecorderOptions, 'mimeType'> & Required<Pick<MediaRecorderOptions, 'mimeType'>>;
|
|
12
17
|
export type AudioRecorderConfig = {
|
|
@@ -7,19 +7,13 @@ import { transcode } from '../transcode';
|
|
|
7
7
|
import { resampleWaveformData } from '../../Attachment';
|
|
8
8
|
import { createFileFromBlobs, getExtensionFromMimeType, getRecordedMediaTypeFromMimeType, } from '../../ReactFileUtilities';
|
|
9
9
|
import { defaultTranslatorFunction } from '../../../i18n';
|
|
10
|
-
import { isSafari } from '../../../utils/browsers';
|
|
11
10
|
import { mergeDeepUndefined } from '../../../utils/mergeDeep';
|
|
12
|
-
const RECORDED_MIME_TYPE_BY_BROWSER = {
|
|
11
|
+
export const RECORDED_MIME_TYPE_BY_BROWSER = {
|
|
13
12
|
audio: {
|
|
14
13
|
others: 'audio/webm',
|
|
15
14
|
safari: 'audio/mp4;codecs=mp4a.40.2',
|
|
16
15
|
},
|
|
17
16
|
};
|
|
18
|
-
export const DEFAULT_MEDIA_RECORDER_CONFIG = {
|
|
19
|
-
mimeType: isSafari()
|
|
20
|
-
? RECORDED_MIME_TYPE_BY_BROWSER.audio.safari
|
|
21
|
-
: RECORDED_MIME_TYPE_BY_BROWSER.audio.others,
|
|
22
|
-
};
|
|
23
17
|
export const DEFAULT_AUDIO_TRANSCODER_CONFIG = {
|
|
24
18
|
sampleRate: 16000,
|
|
25
19
|
};
|
|
@@ -241,7 +235,11 @@ export class MediaRecorderController {
|
|
|
241
235
|
};
|
|
242
236
|
this.t = t || defaultTranslatorFunction;
|
|
243
237
|
this.amplitudeRecorderConfig = mergeDeepUndefined({ ...config?.amplitudeRecorderConfig }, DEFAULT_AMPLITUDE_RECORDER_CONFIG);
|
|
244
|
-
this.mediaRecorderConfig = mergeDeepUndefined({ ...config?.mediaRecorderConfig },
|
|
238
|
+
this.mediaRecorderConfig = mergeDeepUndefined({ ...config?.mediaRecorderConfig }, {
|
|
239
|
+
mimeType: MediaRecorder.isTypeSupported('audio/webm')
|
|
240
|
+
? RECORDED_MIME_TYPE_BY_BROWSER.audio.others
|
|
241
|
+
: RECORDED_MIME_TYPE_BY_BROWSER.audio.safari,
|
|
242
|
+
});
|
|
245
243
|
this.transcoderConfig = mergeDeepUndefined({ ...config?.transcoderConfig }, DEFAULT_AUDIO_TRANSCODER_CONFIG);
|
|
246
244
|
const mediaType = getRecordedMediaTypeFromMimeType(this.mediaRecorderConfig.mimeType);
|
|
247
245
|
if (!mediaType) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { ComponentType } from 'react';
|
|
2
|
-
import { Options } from 'react-markdown';
|
|
3
|
-
import type { PluggableList } from 'react-markdown/lib/react-markdown';
|
|
4
|
-
import type { UserResponse } from 'stream-chat';
|
|
5
2
|
import { MentionProps } from './componentRenderers';
|
|
3
|
+
import type { Options } from 'react-markdown/lib';
|
|
4
|
+
import type { UserResponse } from 'stream-chat';
|
|
5
|
+
import type { PluggableList } from 'unified';
|
|
6
6
|
import type { DefaultStreamChatGenerics } from '../../../types/types';
|
|
7
7
|
export type RenderTextPluginConfigurator = (defaultPlugins: PluggableList) => PluggableList;
|
|
8
8
|
export declare const defaultAllowedTagNames: Array<keyof JSX.IntrinsicElements | 'emoji' | 'mention'>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import ReactMarkdown, {
|
|
2
|
+
import ReactMarkdown, { defaultUrlTransform } from 'react-markdown';
|
|
3
3
|
import { find } from 'linkifyjs';
|
|
4
4
|
import uniqBy from 'lodash.uniqby';
|
|
5
5
|
import remarkGfm from 'remark-gfm';
|
|
@@ -50,7 +50,7 @@ function encodeDecode(url) {
|
|
|
50
50
|
return url;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
const urlTransform = (uri) => uri.startsWith('app://') ? uri :
|
|
53
|
+
const urlTransform = (uri) => uri.startsWith('app://') ? uri : defaultUrlTransform(uri);
|
|
54
54
|
const getPluginsForward = (plugins) => plugins;
|
|
55
55
|
export const markDownRenderers = {
|
|
56
56
|
a: Anchor,
|
|
@@ -118,5 +118,5 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
|
|
|
118
118
|
React.createElement(ReactMarkdown, { allowedElements: allowedTagNames, components: {
|
|
119
119
|
...markDownRenderers,
|
|
120
120
|
...customMarkDownRenderers,
|
|
121
|
-
}, rehypePlugins: getRehypePlugins(rehypePlugins), remarkPlugins: getRemarkPlugins(remarkPlugins), skipHtml: true,
|
|
121
|
+
}, rehypePlugins: getRehypePlugins(rehypePlugins), remarkPlugins: getRemarkPlugins(remarkPlugins), skipHtml: true, unwrapDisallowed: true, urlTransform: urlTransform }, newText)));
|
|
122
122
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
|
2
|
-
import type { AppSettingsAPIResponse, Channel, Mute } from 'stream-chat';
|
|
2
|
+
import type { AppSettingsAPIResponse, Channel, Mute, SearchController } from 'stream-chat';
|
|
3
3
|
import type { ChatProps } from '../components/Chat/Chat';
|
|
4
4
|
import type { DefaultStreamChatGenerics, UnknownType } from '../types/types';
|
|
5
5
|
import type { ChannelsQueryState } from '../components/Chat/hooks/useChannelsQueryState';
|
|
@@ -16,6 +16,8 @@ export type ChatContextValue<StreamChatGenerics extends DefaultStreamChatGeneric
|
|
|
16
16
|
latestMessageDatesByChannels: Record<ChannelCID, Date>;
|
|
17
17
|
mutes: Array<Mute<StreamChatGenerics>>;
|
|
18
18
|
openMobileNav: () => void;
|
|
19
|
+
/** Instance of SearchController class that allows to control all the search operations. */
|
|
20
|
+
searchController: SearchController<StreamChatGenerics>;
|
|
19
21
|
/**
|
|
20
22
|
* Sets active channel to be rendered within Channel component.
|
|
21
23
|
* @param newChannel
|