stream-chat-react 12.7.1 → 12.8.1
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/ChannelList/ChannelList.d.ts +1 -1
- package/dist/components/ChannelList/ChannelList.js +29 -34
- package/dist/components/ChannelList/hooks/index.d.ts +1 -0
- package/dist/components/ChannelList/hooks/index.js +1 -0
- package/dist/components/ChannelList/hooks/useChannelListShape.d.ts +60 -0
- package/dist/components/ChannelList/hooks/useChannelListShape.js +344 -0
- package/dist/components/ChannelList/hooks/useChannelMembershipState.d.ts +2 -0
- package/dist/components/ChannelList/hooks/useChannelMembershipState.js +15 -0
- package/dist/components/ChannelList/utils.d.ts +41 -5
- package/dist/components/ChannelList/utils.js +85 -0
- package/dist/components/ChannelPreview/ChannelPreviewActionButtons.d.ts +6 -0
- package/dist/components/ChannelPreview/ChannelPreviewActionButtons.js +30 -0
- package/dist/components/ChannelPreview/ChannelPreviewMessenger.d.ts +2 -2
- package/dist/components/ChannelPreview/ChannelPreviewMessenger.js +14 -9
- package/dist/components/ChannelPreview/icons.d.ts +6 -0
- package/dist/components/ChannelPreview/icons.js +8 -0
- package/dist/components/ChannelPreview/index.d.ts +1 -0
- package/dist/components/ChannelPreview/index.js +1 -0
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Dialog/DialogManager.d.ts +8 -0
- package/dist/components/Dialog/DialogManager.js +42 -0
- package/dist/components/Dialog/hooks/useDialog.js +5 -1
- package/dist/context/ComponentContext.d.ts +3 -1
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/experimental/index.browser.cjs +1 -1
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs +1 -1
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/i18n/Streami18n.d.ts +2 -0
- package/dist/i18n/de.json +2 -0
- package/dist/i18n/en.json +2 -0
- package/dist/i18n/es.json +2 -0
- package/dist/i18n/fr.json +2 -0
- package/dist/i18n/hi.json +2 -0
- package/dist/i18n/it.json +2 -0
- package/dist/i18n/ja.json +2 -0
- package/dist/i18n/ko.json +2 -0
- package/dist/i18n/nl.json +2 -0
- package/dist/i18n/pt.json +2 -0
- package/dist/i18n/ru.json +2 -0
- package/dist/i18n/tr.json +2 -0
- package/dist/index.browser.cjs +10360 -9733
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +10285 -9650
- package/dist/index.node.cjs.map +4 -4
- package/dist/scss/v2/ChannelPreview/ChannelPreview-layout.scss +24 -0
- package/dist/scss/v2/ChannelPreview/ChannelPreview-theme.scss +18 -0
- package/package.json +4 -4
|
@@ -94,4 +94,4 @@ export type ChannelListProps<StreamChatGenerics extends DefaultStreamChatGeneric
|
|
|
94
94
|
/**
|
|
95
95
|
* Renders a preview list of Channels, allowing you to select the Channel you want to open
|
|
96
96
|
*/
|
|
97
|
-
export declare const ChannelList: <
|
|
97
|
+
export declare const ChannelList: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(props: ChannelListProps<SCG>) => React.JSX.Element;
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { ChannelListMessenger } from './ChannelListMessenger';
|
|
4
|
-
import { useChannelDeletedListener } from './hooks/useChannelDeletedListener';
|
|
5
|
-
import { useChannelHiddenListener } from './hooks/useChannelHiddenListener';
|
|
6
|
-
import { useChannelTruncatedListener } from './hooks/useChannelTruncatedListener';
|
|
7
|
-
import { useChannelUpdatedListener } from './hooks/useChannelUpdatedListener';
|
|
8
|
-
import { useChannelVisibleListener } from './hooks/useChannelVisibleListener';
|
|
9
4
|
import { useConnectionRecoveredListener } from './hooks/useConnectionRecoveredListener';
|
|
10
|
-
import { useMessageNewListener } from './hooks/useMessageNewListener';
|
|
11
5
|
import { useMobileNavigation } from './hooks/useMobileNavigation';
|
|
12
|
-
import { useNotificationAddedToChannelListener } from './hooks/useNotificationAddedToChannelListener';
|
|
13
|
-
import { useNotificationMessageNewListener } from './hooks/useNotificationMessageNewListener';
|
|
14
|
-
import { useNotificationRemovedFromChannelListener } from './hooks/useNotificationRemovedFromChannelListener';
|
|
15
6
|
import { usePaginatedChannels } from './hooks/usePaginatedChannels';
|
|
16
|
-
import {
|
|
17
|
-
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUp } from './utils';
|
|
7
|
+
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUpwards } from './utils';
|
|
18
8
|
import { Avatar as DefaultAvatar } from '../Avatar';
|
|
19
9
|
import { ChannelPreview } from '../ChannelPreview/ChannelPreview';
|
|
20
10
|
import { ChannelSearch as DefaultChannelSearch, } from '../ChannelSearch/ChannelSearch';
|
|
@@ -24,11 +14,12 @@ import { LoadMorePaginator } from '../LoadMore/LoadMorePaginator';
|
|
|
24
14
|
import { NullComponent } from '../UtilityComponents';
|
|
25
15
|
import { ChannelListContextProvider } from '../../context';
|
|
26
16
|
import { useChatContext } from '../../context/ChatContext';
|
|
17
|
+
import { useChannelListShape, usePrepareShapeHandlers } from './hooks/useChannelListShape';
|
|
27
18
|
const DEFAULT_FILTERS = {};
|
|
28
19
|
const DEFAULT_OPTIONS = {};
|
|
29
20
|
const DEFAULT_SORT = {};
|
|
30
21
|
const UnMemoizedChannelList = (props) => {
|
|
31
|
-
const { additionalChannelSearchProps, Avatar = DefaultAvatar, allowNewMessagesFromUnfilteredChannels, channelRenderFilterFn, ChannelSearch = DefaultChannelSearch, customActiveChannel, customQueryChannels, EmptyStateIndicator = DefaultEmptyStateIndicator, filters, getLatestMessagePreview, LoadingErrorIndicator = NullComponent, LoadingIndicator = LoadingChannels, List = ChannelListMessenger, lockChannelOrder, 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;
|
|
22
|
+
const { additionalChannelSearchProps, Avatar = DefaultAvatar, allowNewMessagesFromUnfilteredChannels = true, channelRenderFilterFn, ChannelSearch = DefaultChannelSearch, customActiveChannel, customQueryChannels, EmptyStateIndicator = DefaultEmptyStateIndicator, filters = {}, getLatestMessagePreview, LoadingErrorIndicator = NullComponent, LoadingIndicator = LoadingChannels, List = ChannelListMessenger, 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;
|
|
32
23
|
const { channel, channelsQueryState, client, closeMobileNav, customClasses, navOpen = false, setActiveChannel, theme, useImageFlagEmojisOnWindows, } = useChatContext('ChannelList');
|
|
33
24
|
const channelListRef = useRef(null);
|
|
34
25
|
const [channelUpdateCount, setChannelUpdateCount] = useState(0);
|
|
@@ -42,6 +33,7 @@ const UnMemoizedChannelList = (props) => {
|
|
|
42
33
|
return;
|
|
43
34
|
}
|
|
44
35
|
if (customActiveChannel) {
|
|
36
|
+
// FIXME: this is wrong...
|
|
45
37
|
let customActiveChannelObject = channels.find((chan) => chan.id === customActiveChannel);
|
|
46
38
|
if (!customActiveChannelObject) {
|
|
47
39
|
//@ts-expect-error
|
|
@@ -49,10 +41,10 @@ const UnMemoizedChannelList = (props) => {
|
|
|
49
41
|
}
|
|
50
42
|
if (customActiveChannelObject) {
|
|
51
43
|
setActiveChannel(customActiveChannelObject, watchers);
|
|
52
|
-
const newChannels =
|
|
53
|
-
activeChannel: customActiveChannelObject,
|
|
44
|
+
const newChannels = moveChannelUpwards({
|
|
54
45
|
channels,
|
|
55
|
-
|
|
46
|
+
channelToMove: customActiveChannelObject,
|
|
47
|
+
sort,
|
|
56
48
|
});
|
|
57
49
|
setChannels(newChannels);
|
|
58
50
|
}
|
|
@@ -66,16 +58,9 @@ const UnMemoizedChannelList = (props) => {
|
|
|
66
58
|
* For some events, inner properties on the channel will update but the shallow comparison will not
|
|
67
59
|
* force a re-render. Incrementing this dummy variable ensures the channel previews update.
|
|
68
60
|
*/
|
|
69
|
-
const forceUpdate = useCallback(() => setChannelUpdateCount((count) => count + 1), [
|
|
70
|
-
setChannelUpdateCount,
|
|
71
|
-
]);
|
|
61
|
+
const forceUpdate = useCallback(() => setChannelUpdateCount((count) => count + 1), []);
|
|
72
62
|
const onSearch = useCallback((event) => {
|
|
73
|
-
|
|
74
|
-
setSearchActive(false);
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
setSearchActive(true);
|
|
78
|
-
}
|
|
63
|
+
setSearchActive(!!event.target.value);
|
|
79
64
|
additionalChannelSearchProps?.onSearch?.(event);
|
|
80
65
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
66
|
}, []);
|
|
@@ -87,17 +72,27 @@ const UnMemoizedChannelList = (props) => {
|
|
|
87
72
|
const { channels, hasNextPage, loadNextPage, setChannels } = usePaginatedChannels(client, filters || DEFAULT_FILTERS, sort || DEFAULT_SORT, options || DEFAULT_OPTIONS, activeChannelHandler, recoveryThrottleIntervalMs, customQueryChannels);
|
|
88
73
|
const loadedChannels = channelRenderFilterFn ? channelRenderFilterFn(channels) : channels;
|
|
89
74
|
useMobileNavigation(channelListRef, navOpen, closeMobileNav);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
75
|
+
const { customHandler, defaultHandler } = usePrepareShapeHandlers({
|
|
76
|
+
allowNewMessagesFromUnfilteredChannels,
|
|
77
|
+
filters,
|
|
78
|
+
lockChannelOrder,
|
|
79
|
+
onAddedToChannel,
|
|
80
|
+
onChannelDeleted,
|
|
81
|
+
onChannelHidden,
|
|
82
|
+
onChannelTruncated,
|
|
83
|
+
onChannelUpdated,
|
|
84
|
+
onChannelVisible,
|
|
85
|
+
onMessageNew,
|
|
86
|
+
onMessageNewHandler,
|
|
87
|
+
onRemovedFromChannel,
|
|
88
|
+
setChannels,
|
|
89
|
+
sort,
|
|
90
|
+
// TODO: implement
|
|
91
|
+
// customHandleChannelListShape
|
|
92
|
+
});
|
|
93
|
+
useChannelListShape(customHandler ?? defaultHandler);
|
|
94
|
+
// TODO: maybe move this too
|
|
99
95
|
useConnectionRecoveredListener(forceUpdate);
|
|
100
|
-
useUserPresenceChangedListener(setChannels);
|
|
101
96
|
useEffect(() => {
|
|
102
97
|
const handleEvent = (event) => {
|
|
103
98
|
if (event.cid === channel?.cid) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
import { Channel, Event, ExtendableGenerics } from 'stream-chat';
|
|
3
|
+
import { ChannelListProps } from '../ChannelList';
|
|
4
|
+
type SetChannels<SCG extends ExtendableGenerics> = Dispatch<SetStateAction<Channel<SCG>[]>>;
|
|
5
|
+
type BaseParameters<SCG extends ExtendableGenerics> = {
|
|
6
|
+
event: Event<SCG>;
|
|
7
|
+
setChannels: SetChannels<SCG>;
|
|
8
|
+
};
|
|
9
|
+
type RepeatedParameters<SCG extends ExtendableGenerics> = {
|
|
10
|
+
customHandler?: (setChannels: BaseParameters<SCG>['setChannels'], event: BaseParameters<SCG>['event']) => void;
|
|
11
|
+
};
|
|
12
|
+
type HandleMessageNewParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG> & {
|
|
13
|
+
allowNewMessagesFromUnfilteredChannels: boolean;
|
|
14
|
+
lockChannelOrder: boolean;
|
|
15
|
+
} & Required<Pick<ChannelListProps<SCG>, 'filters' | 'sort'>>;
|
|
16
|
+
type HandleNotificationMessageNewParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG> & {
|
|
17
|
+
allowNewMessagesFromUnfilteredChannels: boolean;
|
|
18
|
+
lockChannelOrder: boolean;
|
|
19
|
+
} & Required<Pick<ChannelListProps<SCG>, 'filters' | 'sort'>>;
|
|
20
|
+
type HandleNotificationRemovedFromChannelParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
|
|
21
|
+
type HandleNotificationAddedToChannelParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG> & {
|
|
22
|
+
allowNewMessagesFromUnfilteredChannels: boolean;
|
|
23
|
+
lockChannelOrder: boolean;
|
|
24
|
+
} & Required<Pick<ChannelListProps<SCG>, 'sort'>>;
|
|
25
|
+
type HandleMemberUpdatedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & {
|
|
26
|
+
lockChannelOrder: boolean;
|
|
27
|
+
} & Required<Pick<ChannelListProps<SCG>, 'sort'>>;
|
|
28
|
+
type HandleChannelDeletedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
|
|
29
|
+
type HandleChannelHiddenParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
|
|
30
|
+
type HandleChannelVisibleParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
|
|
31
|
+
type HandleChannelTruncatedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
|
|
32
|
+
type HandleChannelUpdatedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG> & RepeatedParameters<SCG>;
|
|
33
|
+
type HandleUserPresenceChangedParameters<SCG extends ExtendableGenerics> = BaseParameters<SCG>;
|
|
34
|
+
export declare const useChannelListShapeDefaults: <SCG extends ExtendableGenerics>() => {
|
|
35
|
+
handleChannelDeleted: (p: HandleChannelDeletedParameters<SCG>) => void;
|
|
36
|
+
handleChannelHidden: (p: HandleChannelHiddenParameters<SCG>) => void;
|
|
37
|
+
handleChannelTruncated: ({ customHandler, event, setChannels }: HandleChannelTruncatedParameters<SCG>) => void;
|
|
38
|
+
handleChannelUpdated: ({ customHandler, event, setChannels }: HandleChannelUpdatedParameters<SCG>) => void;
|
|
39
|
+
handleChannelVisible: ({ customHandler, event, setChannels }: HandleChannelVisibleParameters<SCG>) => Promise<void>;
|
|
40
|
+
handleMemberUpdated: ({ event, lockChannelOrder, setChannels, sort }: HandleMemberUpdatedParameters<SCG>) => void;
|
|
41
|
+
handleMessageNew: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, lockChannelOrder, setChannels, sort, }: HandleMessageNewParameters<SCG>) => void;
|
|
42
|
+
handleNotificationAddedToChannel: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, }: HandleNotificationAddedToChannelParameters<SCG>) => Promise<void>;
|
|
43
|
+
handleNotificationMessageNew: ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, setChannels, sort, }: HandleNotificationMessageNewParameters<SCG>) => Promise<void>;
|
|
44
|
+
handleNotificationRemovedFromChannel: ({ customHandler, event, setChannels, }: HandleNotificationRemovedFromChannelParameters<SCG>) => void;
|
|
45
|
+
handleUserPresenceChanged: ({ event, setChannels }: HandleUserPresenceChangedParameters<SCG>) => void;
|
|
46
|
+
};
|
|
47
|
+
type UseDefaultHandleChannelListShapeParameters<SCG extends ExtendableGenerics> = Required<Pick<ChannelListProps<SCG>, 'allowNewMessagesFromUnfilteredChannels' | 'lockChannelOrder' | 'filters' | 'sort'>> & Pick<ChannelListProps<SCG>, 'onAddedToChannel' | 'onChannelDeleted' | 'onChannelHidden' | 'onChannelTruncated' | 'onChannelUpdated' | 'onChannelVisible' | 'onMessageNew' | 'onMessageNewHandler' | 'onRemovedFromChannel'> & {
|
|
48
|
+
setChannels: SetChannels<SCG>;
|
|
49
|
+
customHandleChannelListShape?: (data: {
|
|
50
|
+
defaults: ReturnType<typeof useChannelListShapeDefaults>;
|
|
51
|
+
event: Event<SCG>;
|
|
52
|
+
setChannels: SetChannels<SCG>;
|
|
53
|
+
}) => void;
|
|
54
|
+
};
|
|
55
|
+
export declare const usePrepareShapeHandlers: <SCG extends ExtendableGenerics>({ allowNewMessagesFromUnfilteredChannels, customHandleChannelListShape, filters, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, setChannels, sort, }: UseDefaultHandleChannelListShapeParameters<SCG>) => {
|
|
56
|
+
customHandler: ((e: Event<SCG>) => void) | null;
|
|
57
|
+
defaultHandler: (e: Event<SCG>) => void;
|
|
58
|
+
};
|
|
59
|
+
export declare const useChannelListShape: <SCG extends ExtendableGenerics>(handler: (e: Event<SCG>) => void) => void;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// const defaults = useChannelListShapeDefaults();
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import uniqBy from 'lodash.uniqby';
|
|
4
|
+
import { findLastPinnedChannelIndex, isChannelArchived, isChannelPinned, moveChannelUpwards, shouldConsiderArchivedChannels, shouldConsiderPinnedChannels, } from '../utils';
|
|
5
|
+
import { useChatContext } from '../../../context';
|
|
6
|
+
import { getChannel } from '../../../utils';
|
|
7
|
+
const shared = ({ customHandler, event, setChannels, }) => {
|
|
8
|
+
if (typeof customHandler === 'function') {
|
|
9
|
+
return customHandler(setChannels, event);
|
|
10
|
+
}
|
|
11
|
+
setChannels((channels) => {
|
|
12
|
+
const channelIndex = channels.findIndex((channel) => channel.cid === event.cid);
|
|
13
|
+
if (channelIndex < 0)
|
|
14
|
+
return channels;
|
|
15
|
+
channels.splice(channelIndex, 1);
|
|
16
|
+
return [...channels];
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export const useChannelListShapeDefaults = () => {
|
|
20
|
+
const { client } = useChatContext();
|
|
21
|
+
const handleMessageNew = useCallback(({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, lockChannelOrder, setChannels, sort, }) => {
|
|
22
|
+
if (typeof customHandler === 'function') {
|
|
23
|
+
return customHandler(setChannels, event);
|
|
24
|
+
}
|
|
25
|
+
setChannels((channels) => {
|
|
26
|
+
const targetChannelIndex = channels.findIndex((channel) => channel.cid === event.cid);
|
|
27
|
+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
|
|
28
|
+
const targetChannel = channels[targetChannelIndex];
|
|
29
|
+
const isTargetChannelPinned = isChannelPinned(targetChannel);
|
|
30
|
+
const isTargetChannelArchived = isChannelArchived(targetChannel);
|
|
31
|
+
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
|
|
32
|
+
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
|
|
33
|
+
if (
|
|
34
|
+
// target channel is archived
|
|
35
|
+
(isTargetChannelArchived && considerArchivedChannels) ||
|
|
36
|
+
// target channel is pinned
|
|
37
|
+
(isTargetChannelPinned && considerPinnedChannels) ||
|
|
38
|
+
// list order is locked
|
|
39
|
+
lockChannelOrder ||
|
|
40
|
+
// target channel is not within the loaded list and loading from cache is disallowed
|
|
41
|
+
(!targetChannelExistsWithinList && !allowNewMessagesFromUnfilteredChannels)) {
|
|
42
|
+
return channels;
|
|
43
|
+
}
|
|
44
|
+
// we either have the channel to move or we pull it from the cache (or instantiate) if it's allowed
|
|
45
|
+
const channelToMove = channels[targetChannelIndex] ??
|
|
46
|
+
(allowNewMessagesFromUnfilteredChannels && event.channel_type
|
|
47
|
+
? client.channel(event.channel_type, event.channel_id)
|
|
48
|
+
: null);
|
|
49
|
+
if (channelToMove) {
|
|
50
|
+
return moveChannelUpwards({
|
|
51
|
+
channels,
|
|
52
|
+
channelToMove,
|
|
53
|
+
channelToMoveIndexWithinChannels: targetChannelIndex,
|
|
54
|
+
sort,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return channels;
|
|
58
|
+
});
|
|
59
|
+
}, [client]);
|
|
60
|
+
const handleNotificationMessageNew = useCallback(async ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, filters, setChannels, sort, }) => {
|
|
61
|
+
if (typeof customHandler === 'function') {
|
|
62
|
+
return customHandler(setChannels, event);
|
|
63
|
+
}
|
|
64
|
+
if (!event.channel) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const channel = await getChannel({
|
|
68
|
+
client,
|
|
69
|
+
id: event.channel.id,
|
|
70
|
+
type: event.channel.type,
|
|
71
|
+
});
|
|
72
|
+
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
|
|
73
|
+
if (isChannelArchived(channel) && considerArchivedChannels) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!allowNewMessagesFromUnfilteredChannels) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
setChannels((channels) => moveChannelUpwards({
|
|
80
|
+
channels,
|
|
81
|
+
channelToMove: channel,
|
|
82
|
+
channelToMoveIndexWithinChannels: -1,
|
|
83
|
+
sort,
|
|
84
|
+
}));
|
|
85
|
+
}, [client]);
|
|
86
|
+
const handleNotificationAddedToChannel = useCallback(async ({ allowNewMessagesFromUnfilteredChannels, customHandler, event, setChannels, }) => {
|
|
87
|
+
if (typeof customHandler === 'function') {
|
|
88
|
+
return customHandler(setChannels, event);
|
|
89
|
+
}
|
|
90
|
+
if (allowNewMessagesFromUnfilteredChannels && event.channel?.type) {
|
|
91
|
+
const channel = await getChannel({
|
|
92
|
+
client,
|
|
93
|
+
id: event.channel.id,
|
|
94
|
+
members: event.channel.members?.reduce((acc, { user, user_id }) => {
|
|
95
|
+
const userId = user_id || user?.id;
|
|
96
|
+
if (userId) {
|
|
97
|
+
acc.push(userId);
|
|
98
|
+
}
|
|
99
|
+
return acc;
|
|
100
|
+
}, []),
|
|
101
|
+
type: event.channel.type,
|
|
102
|
+
});
|
|
103
|
+
setChannels((channels) => uniqBy([channel, ...channels], 'cid'));
|
|
104
|
+
}
|
|
105
|
+
}, [client]);
|
|
106
|
+
const handleNotificationRemovedFromChannel = useCallback(({ customHandler, event, setChannels, }) => {
|
|
107
|
+
if (typeof customHandler === 'function') {
|
|
108
|
+
return customHandler(setChannels, event);
|
|
109
|
+
}
|
|
110
|
+
setChannels((channels) => channels.filter((channel) => channel.cid !== event.channel?.cid));
|
|
111
|
+
}, []);
|
|
112
|
+
const handleMemberUpdated = useCallback(({ event, lockChannelOrder, setChannels, sort }) => {
|
|
113
|
+
if (!event.member?.user || event.member.user.id !== client.userID || !event.channel_type) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const member = event.member;
|
|
117
|
+
const channelType = event.channel_type;
|
|
118
|
+
const channelId = event.channel_id;
|
|
119
|
+
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
|
|
120
|
+
// TODO: extract this and consider single property sort object too
|
|
121
|
+
const pinnedAtSort = Array.isArray(sort) ? sort[0]?.pinned_at ?? null : null;
|
|
122
|
+
setChannels((currentChannels) => {
|
|
123
|
+
const targetChannel = client.channel(channelType, channelId);
|
|
124
|
+
// assumes that channel instances are not changing
|
|
125
|
+
const targetChannelIndex = currentChannels.indexOf(targetChannel);
|
|
126
|
+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
|
|
127
|
+
// handle pinning
|
|
128
|
+
if (!considerPinnedChannels || lockChannelOrder)
|
|
129
|
+
return currentChannels;
|
|
130
|
+
const newChannels = [...currentChannels];
|
|
131
|
+
if (targetChannelExistsWithinList) {
|
|
132
|
+
newChannels.splice(targetChannelIndex, 1);
|
|
133
|
+
}
|
|
134
|
+
// handle archiving (remove channel)
|
|
135
|
+
if (typeof member.archived_at === 'string') {
|
|
136
|
+
return newChannels;
|
|
137
|
+
}
|
|
138
|
+
let lastPinnedChannelIndex = null;
|
|
139
|
+
// calculate last pinned channel index only if `pinned_at` sort is set to
|
|
140
|
+
// ascending order or if it's in descending order while the pin is being removed, otherwise
|
|
141
|
+
// we move to the top (index 0)
|
|
142
|
+
if (pinnedAtSort === 1 || (pinnedAtSort === -1 && !member.pinned_at)) {
|
|
143
|
+
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
|
|
144
|
+
}
|
|
145
|
+
const newTargetChannelIndex = typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0;
|
|
146
|
+
// skip re-render if the position of the channel does not change
|
|
147
|
+
if (currentChannels[newTargetChannelIndex] === targetChannel) {
|
|
148
|
+
return currentChannels;
|
|
149
|
+
}
|
|
150
|
+
newChannels.splice(newTargetChannelIndex, 0, targetChannel);
|
|
151
|
+
return newChannels;
|
|
152
|
+
});
|
|
153
|
+
}, [client]);
|
|
154
|
+
const handleChannelDeleted = useCallback((p) => shared(p), []);
|
|
155
|
+
const handleChannelHidden = useCallback((p) => shared(p), []);
|
|
156
|
+
const handleChannelVisible = useCallback(async ({ customHandler, event, setChannels }) => {
|
|
157
|
+
if (typeof customHandler === 'function') {
|
|
158
|
+
return customHandler(setChannels, event);
|
|
159
|
+
}
|
|
160
|
+
if (event.type && event.channel_type && event.channel_id) {
|
|
161
|
+
const channel = await getChannel({
|
|
162
|
+
client,
|
|
163
|
+
id: event.channel_id,
|
|
164
|
+
type: event.channel_type,
|
|
165
|
+
});
|
|
166
|
+
setChannels((channels) => uniqBy([channel, ...channels], 'cid'));
|
|
167
|
+
}
|
|
168
|
+
}, [client]);
|
|
169
|
+
const handleChannelTruncated = useCallback(({ customHandler, event, setChannels }) => {
|
|
170
|
+
if (typeof customHandler === 'function') {
|
|
171
|
+
return customHandler(setChannels, event);
|
|
172
|
+
}
|
|
173
|
+
// TODO: not sure whether this is needed
|
|
174
|
+
setChannels((channels) => [...channels]);
|
|
175
|
+
// if (forceUpdate) {
|
|
176
|
+
// forceUpdate();
|
|
177
|
+
// }
|
|
178
|
+
}, []);
|
|
179
|
+
const handleChannelUpdated = useCallback(({ customHandler, event, setChannels }) => {
|
|
180
|
+
if (typeof customHandler === 'function') {
|
|
181
|
+
return customHandler(setChannels, event);
|
|
182
|
+
}
|
|
183
|
+
setChannels((channels) => {
|
|
184
|
+
const channelIndex = channels.findIndex((channel) => channel.cid === event.channel?.cid);
|
|
185
|
+
if (channelIndex > -1 && event.channel) {
|
|
186
|
+
const newChannels = channels;
|
|
187
|
+
newChannels[channelIndex].data = {
|
|
188
|
+
...event.channel,
|
|
189
|
+
hidden: event.channel?.hidden ?? newChannels[channelIndex].data?.hidden,
|
|
190
|
+
own_capabilities: event.channel?.own_capabilities ?? newChannels[channelIndex].data?.own_capabilities,
|
|
191
|
+
};
|
|
192
|
+
return [...newChannels];
|
|
193
|
+
}
|
|
194
|
+
return channels;
|
|
195
|
+
});
|
|
196
|
+
// if (forceUpdate) {
|
|
197
|
+
// forceUpdate();
|
|
198
|
+
// }
|
|
199
|
+
}, []);
|
|
200
|
+
const handleUserPresenceChanged = useCallback(({ event, setChannels }) => {
|
|
201
|
+
setChannels((channels) => {
|
|
202
|
+
const newChannels = channels.map((channel) => {
|
|
203
|
+
if (!event.user?.id || !channel.state.members[event.user.id]) {
|
|
204
|
+
return channel;
|
|
205
|
+
}
|
|
206
|
+
// FIXME: oh no...
|
|
207
|
+
const newChannel = channel;
|
|
208
|
+
newChannel.state.members[event.user.id].user = event.user;
|
|
209
|
+
return newChannel;
|
|
210
|
+
});
|
|
211
|
+
return newChannels;
|
|
212
|
+
});
|
|
213
|
+
}, []);
|
|
214
|
+
return useMemo(() => ({
|
|
215
|
+
handleChannelDeleted,
|
|
216
|
+
handleChannelHidden,
|
|
217
|
+
handleChannelTruncated,
|
|
218
|
+
handleChannelUpdated,
|
|
219
|
+
handleChannelVisible,
|
|
220
|
+
handleMemberUpdated,
|
|
221
|
+
handleMessageNew,
|
|
222
|
+
handleNotificationAddedToChannel,
|
|
223
|
+
handleNotificationMessageNew,
|
|
224
|
+
handleNotificationRemovedFromChannel,
|
|
225
|
+
handleUserPresenceChanged,
|
|
226
|
+
}), [
|
|
227
|
+
handleChannelDeleted,
|
|
228
|
+
handleChannelHidden,
|
|
229
|
+
handleChannelTruncated,
|
|
230
|
+
handleChannelUpdated,
|
|
231
|
+
handleChannelVisible,
|
|
232
|
+
handleMemberUpdated,
|
|
233
|
+
handleMessageNew,
|
|
234
|
+
handleNotificationAddedToChannel,
|
|
235
|
+
handleNotificationMessageNew,
|
|
236
|
+
handleNotificationRemovedFromChannel,
|
|
237
|
+
handleUserPresenceChanged,
|
|
238
|
+
]);
|
|
239
|
+
};
|
|
240
|
+
export const usePrepareShapeHandlers = ({ allowNewMessagesFromUnfilteredChannels, customHandleChannelListShape, filters, lockChannelOrder, onAddedToChannel, onChannelDeleted, onChannelHidden, onChannelTruncated, onChannelUpdated, onChannelVisible, onMessageNew, onMessageNewHandler, onRemovedFromChannel, setChannels, sort, }) => {
|
|
241
|
+
const defaults = useChannelListShapeDefaults();
|
|
242
|
+
const defaultHandleChannelListShapeRef = useRef();
|
|
243
|
+
const customHandleChannelListShapeRef = useRef();
|
|
244
|
+
customHandleChannelListShapeRef.current = (event) => {
|
|
245
|
+
// @ts-expect-error can't use ReturnType<typeof useChannelListShapeDefaults<SCG>> until we upgrade prettier to at least v2.7.0
|
|
246
|
+
customHandleChannelListShape?.({ defaults, event, setChannels });
|
|
247
|
+
};
|
|
248
|
+
defaultHandleChannelListShapeRef.current = (event) => {
|
|
249
|
+
switch (event.type) {
|
|
250
|
+
case 'message.new':
|
|
251
|
+
defaults.handleMessageNew({
|
|
252
|
+
allowNewMessagesFromUnfilteredChannels,
|
|
253
|
+
customHandler: onMessageNewHandler,
|
|
254
|
+
event,
|
|
255
|
+
filters,
|
|
256
|
+
lockChannelOrder,
|
|
257
|
+
setChannels,
|
|
258
|
+
sort,
|
|
259
|
+
});
|
|
260
|
+
break;
|
|
261
|
+
case 'notification.message_new':
|
|
262
|
+
defaults.handleNotificationMessageNew({
|
|
263
|
+
allowNewMessagesFromUnfilteredChannels,
|
|
264
|
+
customHandler: onMessageNew,
|
|
265
|
+
event,
|
|
266
|
+
filters,
|
|
267
|
+
lockChannelOrder,
|
|
268
|
+
setChannels,
|
|
269
|
+
sort,
|
|
270
|
+
});
|
|
271
|
+
break;
|
|
272
|
+
case 'notification.added_to_channel':
|
|
273
|
+
defaults.handleNotificationAddedToChannel({
|
|
274
|
+
allowNewMessagesFromUnfilteredChannels,
|
|
275
|
+
customHandler: onAddedToChannel,
|
|
276
|
+
event,
|
|
277
|
+
lockChannelOrder,
|
|
278
|
+
setChannels,
|
|
279
|
+
sort,
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
case 'notification.removed_from_channel':
|
|
283
|
+
defaults.handleNotificationRemovedFromChannel({
|
|
284
|
+
customHandler: onRemovedFromChannel,
|
|
285
|
+
event,
|
|
286
|
+
setChannels,
|
|
287
|
+
});
|
|
288
|
+
break;
|
|
289
|
+
case 'channel.deleted':
|
|
290
|
+
defaults.handleChannelDeleted({
|
|
291
|
+
customHandler: onChannelDeleted,
|
|
292
|
+
event,
|
|
293
|
+
setChannels,
|
|
294
|
+
});
|
|
295
|
+
break;
|
|
296
|
+
case 'channel.hidden':
|
|
297
|
+
defaults.handleChannelHidden({ customHandler: onChannelHidden, event, setChannels });
|
|
298
|
+
break;
|
|
299
|
+
case 'channel.visible':
|
|
300
|
+
defaults.handleChannelVisible({ customHandler: onChannelVisible, event, setChannels });
|
|
301
|
+
break;
|
|
302
|
+
case 'channel.truncated':
|
|
303
|
+
defaults.handleChannelTruncated({ customHandler: onChannelTruncated, event, setChannels });
|
|
304
|
+
break;
|
|
305
|
+
case 'channel.updated':
|
|
306
|
+
defaults.handleChannelUpdated({ customHandler: onChannelUpdated, event, setChannels });
|
|
307
|
+
break;
|
|
308
|
+
case 'user.presence.changed':
|
|
309
|
+
defaults.handleUserPresenceChanged({ event, setChannels });
|
|
310
|
+
break;
|
|
311
|
+
case 'member.updated':
|
|
312
|
+
defaults.handleMemberUpdated({
|
|
313
|
+
event,
|
|
314
|
+
lockChannelOrder,
|
|
315
|
+
setChannels,
|
|
316
|
+
sort,
|
|
317
|
+
});
|
|
318
|
+
break;
|
|
319
|
+
default:
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const defaultFn = useCallback((e) => {
|
|
324
|
+
defaultHandleChannelListShapeRef.current?.(e);
|
|
325
|
+
}, []);
|
|
326
|
+
const customFn = useMemo(() => {
|
|
327
|
+
if (!customHandleChannelListShape)
|
|
328
|
+
return null;
|
|
329
|
+
return (e) => {
|
|
330
|
+
customHandleChannelListShapeRef.current?.(e);
|
|
331
|
+
};
|
|
332
|
+
}, [customHandleChannelListShape]);
|
|
333
|
+
return {
|
|
334
|
+
customHandler: customFn,
|
|
335
|
+
defaultHandler: defaultFn,
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
export const useChannelListShape = (handler) => {
|
|
339
|
+
const { client } = useChatContext();
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
const subscription = client.on('all', handler);
|
|
342
|
+
return subscription.unsubscribe;
|
|
343
|
+
}, [client, handler]);
|
|
344
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useChatContext } from '../../../context';
|
|
3
|
+
export const useChannelMembershipState = (channel) => {
|
|
4
|
+
const [membership, setMembership] = useState(channel?.state.membership || {});
|
|
5
|
+
const { client } = useChatContext();
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!channel)
|
|
8
|
+
return;
|
|
9
|
+
const subscriptions = ['member.updated'].map((v) => client.on(v, () => {
|
|
10
|
+
setMembership(channel.state.membership);
|
|
11
|
+
}));
|
|
12
|
+
return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
13
|
+
}, [client, channel]);
|
|
14
|
+
return membership;
|
|
15
|
+
};
|
|
@@ -1,10 +1,46 @@
|
|
|
1
|
-
import type { Channel } from 'stream-chat';
|
|
1
|
+
import type { Channel, ChannelSort, ExtendableGenerics } from 'stream-chat';
|
|
2
2
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
3
|
+
import type { ChannelListProps } from './ChannelList';
|
|
3
4
|
export declare const MAX_QUERY_CHANNELS_LIMIT = 30;
|
|
4
|
-
type MoveChannelUpParams<
|
|
5
|
-
channels: Array<Channel<
|
|
5
|
+
type MoveChannelUpParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
6
|
+
channels: Array<Channel<SCG>>;
|
|
6
7
|
cid: string;
|
|
7
|
-
activeChannel?: Channel<
|
|
8
|
+
activeChannel?: Channel<SCG>;
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated
|
|
12
|
+
*/
|
|
13
|
+
export declare const moveChannelUp: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ activeChannel, channels, cid, }: MoveChannelUpParams<SCG>) => Channel<SCG>[];
|
|
14
|
+
/**
|
|
15
|
+
* Expects channel array sorted by `{ pinned_at: -1 }`.
|
|
16
|
+
*
|
|
17
|
+
* TODO: add support for the `{ pinned_at: 1 }`
|
|
18
|
+
*/
|
|
19
|
+
export declare function findLastPinnedChannelIndex<SCG extends ExtendableGenerics>({ channels, }: {
|
|
20
|
+
channels: Channel<SCG>[];
|
|
21
|
+
}): number | null;
|
|
22
|
+
type MoveChannelUpwardsParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
23
|
+
channels: Array<Channel<SCG>>;
|
|
24
|
+
channelToMove: Channel<SCG>;
|
|
25
|
+
sort: ChannelSort<SCG>;
|
|
26
|
+
/**
|
|
27
|
+
* If the index of the channel within `channels` list which is being moved upwards
|
|
28
|
+
* (`channelToMove`) is known, you can supply it to skip extra calculation.
|
|
29
|
+
*/
|
|
30
|
+
channelToMoveIndexWithinChannels?: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* This function should not be used to move pinned already channels.
|
|
34
|
+
*/
|
|
35
|
+
export declare const moveChannelUpwards: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ channels, channelToMove, channelToMoveIndexWithinChannels, sort, }: MoveChannelUpwardsParams<SCG>) => Channel<SCG>[];
|
|
36
|
+
/**
|
|
37
|
+
* Returns true only if `{ pinned_at: -1 }` or `{ pinned_at: 1 }` option is first within the `sort` array.
|
|
38
|
+
*/
|
|
39
|
+
export declare const shouldConsiderPinnedChannels: <SCG extends ExtendableGenerics>(sort: ChannelListProps<SCG>['sort']) => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Returns `true` only if `archived` property is set to `false` within `filters`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const shouldConsiderArchivedChannels: <SCG extends ExtendableGenerics>(filters: ChannelListProps<SCG>['filters']) => boolean;
|
|
44
|
+
export declare const isChannelPinned: <SCG extends ExtendableGenerics>(channel: Channel<SCG>) => boolean;
|
|
45
|
+
export declare const isChannelArchived: <SCG extends ExtendableGenerics>(channel: Channel<SCG>) => boolean;
|
|
10
46
|
export {};
|