diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx
index 32fc8fe0fb..026811844c 100644
--- a/src/components/Channel/Channel.tsx
+++ b/src/components/Channel/Channel.tsx
@@ -81,7 +81,7 @@ import { findInMsgSetByDate, findInMsgSetById, makeAddNotifications } from './ut
import { useThreadContext } from '../Threads';
import { getChannel } from '../../utils';
import type {
- ChannelUnreadUiState,
+ // ChannelUnreadUiState,
GiphyVersions,
ImageAttachmentSizeHandler,
VideoAttachmentSizeHandler,
@@ -187,7 +187,7 @@ export type ChannelProps = ChannelPropsForwardedToComponentContext & {
/** Custom action handler to override the default `channel.markRead` request function (advanced usage only) */
doMarkReadRequest?: (
channel: StreamChannel,
- setChannelUnreadUiState?: (state: ChannelUnreadUiState) => void,
+ // setChannelUnreadUiState?: (state: ChannelUnreadUiState) => void,
) => Promise | void;
/** Custom action handler to override the default `channel.sendMessage` request function (advanced usage only) */
doSendMessageRequest?: (
@@ -329,22 +329,22 @@ const ChannelInner = (
const [notifications, setNotifications] = useState([]);
const notificationTimeouts = useRef>([]);
- const [channelUnreadUiState, _setChannelUnreadUiState] =
- useState();
-
- const channelReducer = useMemo(() => makeChannelReducer(), []);
-
- const [state, dispatch] = useReducer(
- channelReducer,
- // channel.initialized === false if client.channel().query() was not called, e.g. ChannelList is not used
- // => Channel will call channel.watch() in useLayoutEffect => state.loading is used to signal the watch() call state
- {
- ...initialState,
- hasMore: channel.state.messagePagination.hasPrev,
- loading: !channel.initialized,
- messages: channel.state.messages,
- },
- );
+ // const [channelUnreadUiState, _setChannelUnreadUiState] =
+ // useState();
+
+ // const channelReducer = useMemo(() => makeChannelReducer(), []);
+
+ // const [state, dispatch] = useReducer(
+ // channelReducer,
+ // // channel.initialized === false if client.channel().query() was not called, e.g. ChannelList is not used
+ // // => Channel will call channel.watch() in useLayoutEffect => state.loading is used to signal the watch() call state
+ // {
+ // ...initialState,
+ // hasMore: channel.state.messagePagination.hasPrev,
+ // loading: !channel.initialized,
+ // messages: channel.state.messages,
+ // },
+ // );
const jumpToMessageFromSearch = useSearchFocusedMessage();
const isMounted = useIsMounted();
@@ -358,23 +358,23 @@ const ChannelInner = (
const channelCapabilitiesArray = channel.data?.own_capabilities as string[];
- const throttledCopyStateFromChannel = throttle(
- () => dispatch({ channel, type: 'copyStateFromChannelOnEvent' }),
- 500,
- {
- leading: true,
- trailing: true,
- },
- );
-
- const setChannelUnreadUiState = useMemo(
- () =>
- throttle(_setChannelUnreadUiState, 200, {
- leading: true,
- trailing: false,
- }),
- [],
- );
+ // const throttledCopyStateFromChannel = throttle(
+ // () => dispatch({ channel, type: 'copyStateFromChannelOnEvent' }),
+ // 500,
+ // {
+ // leading: true,
+ // trailing: true,
+ // },
+ // );
+
+ // const setChannelUnreadUiState = useMemo(
+ // () =>
+ // throttle(_setChannelUnreadUiState, 200, {
+ // leading: true,
+ // trailing: false,
+ // }),
+ // [],
+ // );
const markRead = useMemo(
() =>
@@ -391,17 +391,18 @@ const ChannelInner = (
if (doMarkReadRequest) {
doMarkReadRequest(
channel,
- updateChannelUiUnreadState ? setChannelUnreadUiState : undefined,
+ // updateChannelUiUnreadState ? setChannelUnreadUiState : undefined,
);
} else {
const markReadResponse = await channel.markRead();
// markReadResponse.event can be null in case of a user that is not a member of a channel being marked read
// in that case event is null and we should not set unread UI
if (updateChannelUiUnreadState && markReadResponse?.event) {
- _setChannelUnreadUiState({
- last_read: lastRead.current,
- last_read_message_id: markReadResponse.event.last_read_message_id,
- unread_messages: 0,
+ channel.messagePaginator.unreadStateSnapshot.next({
+ firstUnreadMessageId: null,
+ lastReadAt: lastRead.current,
+ lastReadMessageId: markReadResponse.event.last_read_message_id ?? null,
+ unreadCount: 0,
});
}
}
@@ -423,18 +424,18 @@ const ChannelInner = (
channel,
channelConfig,
doMarkReadRequest,
- setChannelUnreadUiState,
+ // setChannelUnreadUiState,
t,
],
);
const handleEvent = async (event: Event) => {
if (event.message) {
- dispatch({
- channel,
- message: event.message,
- type: 'updateThreadOnEvent',
- });
+ // dispatch({
+ // channel,
+ // message: event.message,
+ // type: 'updateThreadOnEvent',
+ // });
}
// ignore the event if it is not targeted at the current channel.
@@ -445,9 +446,9 @@ const ChannelInner = (
if (event.type === 'user.watching.start' || event.type === 'user.watching.stop')
return;
- if (event.type === 'typing.start' || event.type === 'typing.stop') {
- return dispatch({ channel, type: 'setTyping' });
- }
+ // if (event.type === 'typing.start' || event.type === 'typing.stop') {
+ // return dispatch({ channel, type: 'setTyping' });
+ // }
if (event.type === 'connection.changed' && typeof event.online === 'boolean') {
online.current = event.online;
@@ -503,22 +504,22 @@ const ChannelInner = (
});
}
- if (event.type === 'notification.mark_unread')
- _setChannelUnreadUiState((prev) => {
- if (!(event.last_read_at && event.user)) return prev;
- return {
- first_unread_message_id: event.first_unread_message_id,
- last_read: new Date(event.last_read_at),
- last_read_message_id: event.last_read_message_id,
- unread_messages: event.unread_messages ?? 0,
- };
- });
-
- if (event.type === 'channel.truncated' && event.cid === channel.cid) {
- _setChannelUnreadUiState(undefined);
- }
-
- throttledCopyStateFromChannel();
+ // if (event.type === 'notification.mark_unread')
+ // _setChannelUnreadUiState((prev) => {
+ // if (!(event.last_read_at && event.user)) return prev;
+ // return {
+ // first_unread_message_id: event.first_unread_message_id,
+ // last_read: new Date(event.last_read_at),
+ // last_read_message_id: event.last_read_message_id,
+ // unread_messages: event.unread_messages ?? 0,
+ // };
+ // });
+
+ // if (event.type === 'channel.truncated' && event.cid === channel.cid) {
+ // _setChannelUnreadUiState(undefined);
+ // }
+
+ // throttledCopyStateFromChannel();
};
// useLayoutEffect here to prevent spinner. Use Suspense when it is available in stable release
@@ -551,7 +552,7 @@ const ChannelInner = (
const config = channel.getConfig();
setChannelConfig(config);
} catch (e) {
- dispatch({ error: e as Error, type: 'setError' });
+ // dispatch({ error: e as Error, type: 'setError' });
errored = true;
}
}
@@ -560,17 +561,17 @@ const ChannelInner = (
originalTitle.current = document.title;
if (!errored) {
- dispatch({
- channel,
- hasMore: channel.state.messagePagination.hasPrev,
- type: 'initStateFromChannel',
- });
-
- if (client.user?.id && channel.state.read[client.user.id]) {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { user, ...ownReadState } = channel.state.read[client.user.id];
- _setChannelUnreadUiState(ownReadState);
- }
+ // dispatch({
+ // channel,
+ // hasMore: channel.state.messagePagination.hasPrev,
+ // type: 'initStateFromChannel',
+ // });
+
+ // if (client.user?.id && channel.state.read[client.user.id]) {
+ // // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // const { user, ...ownReadState } = channel.state.read[client.user.id];
+ // _setChannelUnreadUiState(ownReadState);
+ // }
/**
* TODO: maybe pass last_read to the countUnread method to get proper value
* combined with channel.countUnread adjustment (_countMessageAsUnread)
@@ -608,13 +609,13 @@ const ChannelInner = (
initializeOnMount,
]);
- useEffect(() => {
- if (!state.thread) return;
-
- const message = state.messages?.find((m) => m.id === state.thread?.id);
-
- if (message) dispatch({ message, type: 'setThread' });
- }, [state.messages, state.thread]);
+ // useEffect(() => {
+ // if (!state.thread) return;
+ //
+ // const message = state.messages?.find((m) => m.id === state.thread?.id);
+ //
+ // if (message) dispatch({ message, type: 'setThread' });
+ // }, [state.messages, state.thread]);
const handleHighlightedMessageChange = useCallback(
({
@@ -624,11 +625,11 @@ const ChannelInner = (
highlightedMessageId: string;
highlightDuration?: number;
}) => {
- dispatch({
- channel,
- highlightedMessageId,
- type: 'jumpToMessageFinished',
- });
+ // dispatch({
+ // channel,
+ // highlightedMessageId,
+ // type: 'jumpToMessageFinished',
+ // });
if (clearHighlightedMessageTimeoutId.current) {
clearTimeout(clearHighlightedMessageTimeoutId.current);
}
@@ -637,10 +638,10 @@ const ChannelInner = (
searchController._internalState.partialNext({ focusedMessage: undefined });
}
clearHighlightedMessageTimeoutId.current = null;
- dispatch({ type: 'clearHighlightedMessage' });
+ // dispatch({ type: 'clearHighlightedMessage' });
}, highlightDuration ?? DEFAULT_HIGHLIGHT_DURATION);
},
- [channel, searchController],
+ [searchController._internalState],
);
useEffect(() => {
@@ -656,266 +657,265 @@ const ChannelInner = (
[],
);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const loadMoreFinished = useCallback(
- debounce(
- (hasMore: boolean, messages: ChannelState['messages']) => {
- if (!isMounted.current) return;
- dispatch({ hasMore, messages, type: 'loadMoreFinished' });
- },
- 2000,
- { leading: true, trailing: true },
- ),
- [],
- );
-
- const loadMore = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
- if (
- !online.current ||
- !window.navigator.onLine ||
- !channel.state.messagePagination.hasPrev
- )
- return 0;
-
- // prevent duplicate loading events...
- const oldestMessage = state?.messages?.[0];
-
- if (
- state.loadingMore ||
- state.loadingMoreNewer ||
- oldestMessage?.status !== 'received'
- ) {
- return 0;
- }
-
- dispatch({ loadingMore: true, type: 'setLoadingMore' });
-
- const oldestID = oldestMessage?.id;
- const perPage = limit;
- let queryResponse: ChannelAPIResponse;
-
- try {
- queryResponse = await channel.query({
- messages: { id_lt: oldestID, limit: perPage },
- watchers: { limit: perPage },
- });
- } catch (e) {
- console.warn('message pagination request failed with error', e);
- dispatch({ loadingMore: false, type: 'setLoadingMore' });
- return 0;
- }
-
- loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
-
- return queryResponse.messages.length;
- };
-
- const loadMoreNewer = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
- if (
- !online.current ||
- !window.navigator.onLine ||
- !channel.state.messagePagination.hasNext
- )
- return 0;
-
- const newestMessage = state?.messages?.[state?.messages?.length - 1];
- if (state.loadingMore || state.loadingMoreNewer) return 0;
-
- dispatch({ loadingMoreNewer: true, type: 'setLoadingMoreNewer' });
-
- const newestId = newestMessage?.id;
- const perPage = limit;
- let queryResponse: ChannelAPIResponse;
-
- try {
- queryResponse = await channel.query({
- messages: { id_gt: newestId, limit: perPage },
- watchers: { limit: perPage },
- });
- } catch (e) {
- console.warn('message pagination request failed with error', e);
- dispatch({ loadingMoreNewer: false, type: 'setLoadingMoreNewer' });
- return 0;
- }
-
- dispatch({
- hasMoreNewer: channel.state.messagePagination.hasNext,
- messages: channel.state.messages,
- type: 'loadMoreNewerFinished',
- });
- return queryResponse.messages.length;
- };
-
- const jumpToMessage: ChannelActionContextValue['jumpToMessage'] = useCallback(
- async (
- messageId,
- messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE,
- highlightDuration = DEFAULT_HIGHLIGHT_DURATION,
- ) => {
- dispatch({ loadingMore: true, type: 'setLoadingMore' });
- await channel.state.loadMessageIntoState(messageId, undefined, messageLimit);
-
- loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
- handleHighlightedMessageChange({
- highlightDuration,
- highlightedMessageId: messageId,
- });
- },
- [channel, handleHighlightedMessageChange, loadMoreFinished],
- );
-
- const jumpToLatestMessage: ChannelActionContextValue['jumpToLatestMessage'] =
- useCallback(async () => {
- await channel.state.loadMessageIntoState('latest');
- loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
- dispatch({
- type: 'jumpToLatestMessage',
- });
- }, [channel, loadMoreFinished]);
-
- const jumpToFirstUnreadMessage: ChannelActionContextValue['jumpToFirstUnreadMessage'] =
- useCallback(
- async (
- queryMessageLimit = DEFAULT_JUMP_TO_PAGE_SIZE,
- highlightDuration = DEFAULT_HIGHLIGHT_DURATION,
- ) => {
- if (!channelUnreadUiState?.unread_messages) return;
- let lastReadMessageId = channelUnreadUiState?.last_read_message_id;
- let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id;
- let isInCurrentMessageSet = false;
-
- if (firstUnreadMessageId) {
- const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages);
- isInCurrentMessageSet = result.index !== -1;
- } else if (lastReadMessageId) {
- const result = findInMsgSetById(lastReadMessageId, channel.state.messages);
- isInCurrentMessageSet = !!result.target;
- firstUnreadMessageId =
- result.index > -1 ? channel.state.messages[result.index + 1]?.id : undefined;
- } else {
- const lastReadTimestamp = channelUnreadUiState.last_read.getTime();
- const { index: lastReadMessageIndex, target: lastReadMessage } =
- findInMsgSetByDate(
- channelUnreadUiState.last_read,
- channel.state.messages,
- true,
- );
-
- if (lastReadMessage) {
- firstUnreadMessageId = channel.state.messages[lastReadMessageIndex + 1]?.id;
- isInCurrentMessageSet = !!firstUnreadMessageId;
- lastReadMessageId = lastReadMessage.id;
- } else {
- dispatch({ loadingMore: true, type: 'setLoadingMore' });
- let messages;
- try {
- messages = (
- await channel.query(
- {
- messages: {
- created_at_around: channelUnreadUiState.last_read.toISOString(),
- limit: queryMessageLimit,
- },
- },
- 'new',
- )
- ).messages;
- } catch (e) {
- addNotification(t('Failed to jump to the first unread message'), 'error');
- loadMoreFinished(
- channel.state.messagePagination.hasPrev,
- channel.state.messages,
- );
- return;
- }
-
- const firstMessageWithCreationDate = messages.find((msg) => msg.created_at);
- if (!firstMessageWithCreationDate) {
- addNotification(t('Failed to jump to the first unread message'), 'error');
- loadMoreFinished(
- channel.state.messagePagination.hasPrev,
- channel.state.messages,
- );
- return;
- }
- const firstMessageTimestamp = new Date(
- firstMessageWithCreationDate.created_at as string,
- ).getTime();
- if (lastReadTimestamp < firstMessageTimestamp) {
- // whole channel is unread
- firstUnreadMessageId = firstMessageWithCreationDate.id;
- } else {
- const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages);
- lastReadMessageId = result.target?.id;
- }
- loadMoreFinished(
- channel.state.messagePagination.hasPrev,
- channel.state.messages,
- );
- }
- }
-
- if (!firstUnreadMessageId && !lastReadMessageId) {
- addNotification(t('Failed to jump to the first unread message'), 'error');
- return;
- }
-
- if (!isInCurrentMessageSet) {
- dispatch({ loadingMore: true, type: 'setLoadingMore' });
- try {
- const targetId = (firstUnreadMessageId ?? lastReadMessageId) as string;
- await channel.state.loadMessageIntoState(
- targetId,
- undefined,
- queryMessageLimit,
- );
- /**
- * if the index of the last read message on the page is beyond the half of the page,
- * we have arrived to the oldest page of the channel
- */
- const indexOfTarget = channel.state.messages.findIndex(
- (message) => message.id === targetId,
- ) as number;
- loadMoreFinished(
- channel.state.messagePagination.hasPrev,
- channel.state.messages,
- );
- firstUnreadMessageId =
- firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id;
- } catch (e) {
- addNotification(t('Failed to jump to the first unread message'), 'error');
- loadMoreFinished(
- channel.state.messagePagination.hasPrev,
- channel.state.messages,
- );
- return;
- }
- }
-
- if (!firstUnreadMessageId) {
- addNotification(t('Failed to jump to the first unread message'), 'error');
- return;
- }
- if (!channelUnreadUiState.first_unread_message_id)
- _setChannelUnreadUiState({
- ...channelUnreadUiState,
- first_unread_message_id: firstUnreadMessageId,
- last_read_message_id: lastReadMessageId,
- });
- handleHighlightedMessageChange({
- highlightDuration,
- highlightedMessageId: firstUnreadMessageId,
- });
- },
- [
- addNotification,
- channel,
- handleHighlightedMessageChange,
- loadMoreFinished,
- t,
- channelUnreadUiState,
- ],
- );
+ // const loadMoreFinished = useCallback(
+ // debounce(
+ // (hasMore: boolean, messages: ChannelState['messages']) => {
+ // if (!isMounted.current) return;
+ // dispatch({ hasMore, messages, type: 'loadMoreFinished' });
+ // },
+ // 2000,
+ // { leading: true, trailing: true },
+ // ),
+ // [],
+ // );
+
+ // const loadMore = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
+ // if (
+ // !online.current ||
+ // !window.navigator.onLine ||
+ // !channel.state.messagePagination.hasPrev
+ // )
+ // return 0;
+ //
+ // // prevent duplicate loading events...
+ // const oldestMessage = state?.messages?.[0];
+ //
+ // if (
+ // state.loadingMore ||
+ // state.loadingMoreNewer ||
+ // oldestMessage?.status !== 'received'
+ // ) {
+ // return 0;
+ // }
+ //
+ // dispatch({ loadingMore: true, type: 'setLoadingMore' });
+ //
+ // const oldestID = oldestMessage?.id;
+ // const perPage = limit;
+ // let queryResponse: ChannelAPIResponse;
+ //
+ // try {
+ // queryResponse = await channel.query({
+ // messages: { id_lt: oldestID, limit: perPage },
+ // watchers: { limit: perPage },
+ // });
+ // } catch (e) {
+ // console.warn('message pagination request failed with error', e);
+ // dispatch({ loadingMore: false, type: 'setLoadingMore' });
+ // return 0;
+ // }
+ //
+ // loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
+ //
+ // return queryResponse.messages.length;
+ // };
+
+ // const loadMoreNewer = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
+ // if (
+ // !online.current ||
+ // !window.navigator.onLine ||
+ // !channel.state.messagePagination.hasNext
+ // )
+ // return 0;
+ //
+ // const newestMessage = state?.messages?.[state?.messages?.length - 1];
+ // if (state.loadingMore || state.loadingMoreNewer) return 0;
+ //
+ // dispatch({ loadingMoreNewer: true, type: 'setLoadingMoreNewer' });
+ //
+ // const newestId = newestMessage?.id;
+ // const perPage = limit;
+ // let queryResponse: ChannelAPIResponse;
+ //
+ // try {
+ // queryResponse = await channel.query({
+ // messages: { id_gt: newestId, limit: perPage },
+ // watchers: { limit: perPage },
+ // });
+ // } catch (e) {
+ // console.warn('message pagination request failed with error', e);
+ // dispatch({ loadingMoreNewer: false, type: 'setLoadingMoreNewer' });
+ // return 0;
+ // }
+ //
+ // dispatch({
+ // hasMoreNewer: channel.state.messagePagination.hasNext,
+ // messages: channel.state.messages,
+ // type: 'loadMoreNewerFinished',
+ // });
+ // return queryResponse.messages.length;
+ // };
+
+ // const jumpToMessage: ChannelActionContextValue['jumpToMessage'] = useCallback(
+ // async (
+ // messageId,
+ // messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE,
+ // highlightDuration = DEFAULT_HIGHLIGHT_DURATION,
+ // ) => {
+ // dispatch({ loadingMore: true, type: 'setLoadingMore' });
+ // await channel.state.loadMessageIntoState(messageId, undefined, messageLimit);
+ //
+ // loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
+ // handleHighlightedMessageChange({
+ // highlightDuration,
+ // highlightedMessageId: messageId,
+ // });
+ // },
+ // [channel, handleHighlightedMessageChange, loadMoreFinished],
+ // );
+
+ // const jumpToLatestMessage: ChannelActionContextValue['jumpToLatestMessage'] =
+ // useCallback(async () => {
+ // await channel.state.loadMessageIntoState('latest');
+ // loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
+ // dispatch({
+ // type: 'jumpToLatestMessage',
+ // });
+ // }, [channel, loadMoreFinished]);
+ //
+ // const jumpToFirstUnreadMessage: ChannelActionContextValue['jumpToFirstUnreadMessage'] =
+ // useCallback(
+ // async (
+ // queryMessageLimit = DEFAULT_JUMP_TO_PAGE_SIZE,
+ // highlightDuration = DEFAULT_HIGHLIGHT_DURATION,
+ // ) => {
+ // if (!channelUnreadUiState?.unread_messages) return;
+ // let lastReadMessageId = channelUnreadUiState?.last_read_message_id;
+ // let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id;
+ // let isInCurrentMessageSet = false;
+ //
+ // if (firstUnreadMessageId) {
+ // const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages);
+ // isInCurrentMessageSet = result.index !== -1;
+ // } else if (lastReadMessageId) {
+ // const result = findInMsgSetById(lastReadMessageId, channel.state.messages);
+ // isInCurrentMessageSet = !!result.target;
+ // firstUnreadMessageId =
+ // result.index > -1 ? channel.state.messages[result.index + 1]?.id : undefined;
+ // } else {
+ // const lastReadTimestamp = channelUnreadUiState.last_read.getTime();
+ // const { index: lastReadMessageIndex, target: lastReadMessage } =
+ // findInMsgSetByDate(
+ // channelUnreadUiState.last_read,
+ // channel.state.messages,
+ // true,
+ // );
+ //
+ // if (lastReadMessage) {
+ // firstUnreadMessageId = channel.state.messages[lastReadMessageIndex + 1]?.id;
+ // isInCurrentMessageSet = !!firstUnreadMessageId;
+ // lastReadMessageId = lastReadMessage.id;
+ // } else {
+ // dispatch({ loadingMore: true, type: 'setLoadingMore' });
+ // let messages;
+ // try {
+ // messages = (
+ // await channel.query(
+ // {
+ // messages: {
+ // created_at_around: channelUnreadUiState.last_read.toISOString(),
+ // limit: queryMessageLimit,
+ // },
+ // },
+ // 'new',
+ // )
+ // ).messages;
+ // } catch (e) {
+ // addNotification(t('Failed to jump to the first unread message'), 'error');
+ // loadMoreFinished(
+ // channel.state.messagePagination.hasPrev,
+ // channel.state.messages,
+ // );
+ // return;
+ // }
+ //
+ // const firstMessageWithCreationDate = messages.find((msg) => msg.created_at);
+ // if (!firstMessageWithCreationDate) {
+ // addNotification(t('Failed to jump to the first unread message'), 'error');
+ // loadMoreFinished(
+ // channel.state.messagePagination.hasPrev,
+ // channel.state.messages,
+ // );
+ // return;
+ // }
+ // const firstMessageTimestamp = new Date(
+ // firstMessageWithCreationDate.created_at as string,
+ // ).getTime();
+ // if (lastReadTimestamp < firstMessageTimestamp) {
+ // // whole channel is unread
+ // firstUnreadMessageId = firstMessageWithCreationDate.id;
+ // } else {
+ // const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages);
+ // lastReadMessageId = result.target?.id;
+ // }
+ // loadMoreFinished(
+ // channel.state.messagePagination.hasPrev,
+ // channel.state.messages,
+ // );
+ // }
+ // }
+ //
+ // if (!firstUnreadMessageId && !lastReadMessageId) {
+ // addNotification(t('Failed to jump to the first unread message'), 'error');
+ // return;
+ // }
+ //
+ // if (!isInCurrentMessageSet) {
+ // dispatch({ loadingMore: true, type: 'setLoadingMore' });
+ // try {
+ // const targetId = (firstUnreadMessageId ?? lastReadMessageId) as string;
+ // await channel.state.loadMessageIntoState(
+ // targetId,
+ // undefined,
+ // queryMessageLimit,
+ // );
+ // /**
+ // * if the index of the last read message on the page is beyond the half of the page,
+ // * we have arrived to the oldest page of the channel
+ // */
+ // const indexOfTarget = channel.state.messages.findIndex(
+ // (message) => message.id === targetId,
+ // ) as number;
+ // loadMoreFinished(
+ // channel.state.messagePagination.hasPrev,
+ // channel.state.messages,
+ // );
+ // firstUnreadMessageId =
+ // firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id;
+ // } catch (e) {
+ // addNotification(t('Failed to jump to the first unread message'), 'error');
+ // loadMoreFinished(
+ // channel.state.messagePagination.hasPrev,
+ // channel.state.messages,
+ // );
+ // return;
+ // }
+ // }
+ //
+ // if (!firstUnreadMessageId) {
+ // addNotification(t('Failed to jump to the first unread message'), 'error');
+ // return;
+ // }
+ // if (!channelUnreadUiState.first_unread_message_id)
+ // _setChannelUnreadUiState({
+ // ...channelUnreadUiState,
+ // first_unread_message_id: firstUnreadMessageId,
+ // last_read_message_id: lastReadMessageId,
+ // });
+ // handleHighlightedMessageChange({
+ // highlightDuration,
+ // highlightedMessageId: firstUnreadMessageId,
+ // });
+ // },
+ // [
+ // addNotification,
+ // channel,
+ // handleHighlightedMessageChange,
+ // loadMoreFinished,
+ // t,
+ // channelUnreadUiState,
+ // ],
+ // );
const deleteMessage = useCallback(
async (
@@ -942,218 +942,217 @@ const ChannelInner = (
// add the message to the local channel state
channel.state.addMessageSorted(updatedMessage, true);
- dispatch({
- channel,
- parentId: state.thread && updatedMessage.parent_id,
- type: 'copyMessagesFromChannel',
- });
+ // dispatch({
+ // channel,
+ // parentId: state.thread && updatedMessage.parent_id,
+ // type: 'copyMessagesFromChannel',
+ // });
};
- const doSendMessage = async ({
- localMessage,
- message,
- options,
- }: {
- localMessage: LocalMessage;
- message: Message;
- options?: SendMessageOptions;
- }) => {
- try {
- let messageResponse: void | SendMessageAPIResponse;
-
- if (doSendMessageRequest) {
- messageResponse = await doSendMessageRequest(channel, message, options);
- } else {
- messageResponse = await channel.sendMessage(message, options);
- }
-
- let existingMessage: LocalMessage | undefined = undefined;
- for (let i = channel.state.messages.length - 1; i >= 0; i--) {
- const msg = channel.state.messages[i];
- if (msg.id && msg.id === message.id) {
- existingMessage = msg;
- break;
- }
- }
-
- const responseTimestamp = new Date(
- messageResponse?.message?.updated_at || 0,
- ).getTime();
- const existingMessageTimestamp = existingMessage?.updated_at?.getTime() || 0;
- const responseIsTheNewest = responseTimestamp > existingMessageTimestamp;
-
- // Replace the message payload after send is completed
- // We need to check for the newest message payload, because on slow network, the response can arrive later than WS events message.new, message.updated.
- // Always override existing message in status "sending"
- if (
- messageResponse?.message &&
- (responseIsTheNewest || existingMessage?.status === 'sending')
- ) {
- updateMessage({
- ...messageResponse.message,
- status: 'received',
- });
- }
- } catch (error) {
- // error response isn't usable so needs to be stringified then parsed
- const stringError = JSON.stringify(error);
- const parsedError = (
- stringError ? JSON.parse(stringError) : {}
- ) as ErrorFromResponse;
-
- // Handle the case where the message already exists
- // (typically, when retrying to send a message).
- // If the message already exists, we can assume it was sent successfully,
- // so we update the message status to "received".
- // Right now, the only way to check this error is by checking
- // the combination of the error code and the error description,
- // since there is no special error code for duplicate messages.
- if (
- parsedError.code === 4 &&
- error instanceof Error &&
- error.message.includes('already exists')
- ) {
- updateMessage({
- ...localMessage,
- status: 'received',
- });
- } else {
- updateMessage({
- ...localMessage,
- error: parsedError,
- status: 'failed',
- });
-
- thread?.upsertReplyLocally({
- message: {
- ...localMessage,
- error: parsedError,
- status: 'failed',
- },
- });
- }
- }
- };
-
- const sendMessage = async ({
- localMessage,
- message,
- options,
- }: {
- localMessage: LocalMessage;
- message: Message;
- options?: SendMessageOptions;
- }) => {
- channel.state.filterErrorMessages();
-
- thread?.upsertReplyLocally({
- message: localMessage,
- });
-
- updateMessage(localMessage);
-
- await doSendMessage({ localMessage, message, options });
- };
-
- const retrySendMessage = async (localMessage: LocalMessage) => {
- updateMessage({
- ...localMessage,
- error: undefined,
- status: 'sending',
- });
-
- await doSendMessage({
- localMessage,
- message: localMessageToNewMessagePayload(localMessage),
- });
- };
+ // const doSendMessage = async ({
+ // localMessage,
+ // message,
+ // options,
+ // }: {
+ // localMessage: LocalMessage;
+ // message: Message;
+ // options?: SendMessageOptions;
+ // }) => {
+ // try {
+ // let messageResponse: void | SendMessageAPIResponse;
+ //
+ // if (doSendMessageRequest) {
+ // messageResponse = await doSendMessageRequest(channel, message, options);
+ // } else {
+ // messageResponse = await channel.sendMessage(message, options);
+ // }
+ //
+ // let existingMessage: LocalMessage | undefined = undefined;
+ // for (let i = channel.state.messages.length - 1; i >= 0; i--) {
+ // const msg = channel.state.messages[i];
+ // if (msg.id && msg.id === message.id) {
+ // existingMessage = msg;
+ // break;
+ // }
+ // }
+ //
+ // const responseTimestamp = new Date(
+ // messageResponse?.message?.updated_at || 0,
+ // ).getTime();
+ // const existingMessageTimestamp = existingMessage?.updated_at?.getTime() || 0;
+ // const responseIsTheNewest = responseTimestamp > existingMessageTimestamp;
+ //
+ // // Replace the message payload after send is completed
+ // // We need to check for the newest message payload, because on slow network, the response can arrive later than WS events message.new, message.updated.
+ // // Always override existing message in status "sending"
+ // if (
+ // messageResponse?.message &&
+ // (responseIsTheNewest || existingMessage?.status === 'sending')
+ // ) {
+ // updateMessage({
+ // ...messageResponse.message,
+ // status: 'received',
+ // });
+ // }
+ // } catch (error) {
+ // // error response isn't usable so needs to be stringified then parsed
+ // const stringError = JSON.stringify(error);
+ // const parsedError = (
+ // stringError ? JSON.parse(stringError) : {}
+ // ) as ErrorFromResponse;
+ //
+ // // Handle the case where the message already exists
+ // // (typically, when retrying to send a message).
+ // // If the message already exists, we can assume it was sent successfully,
+ // // so we update the message status to "received".
+ // // Right now, the only way to check this error is by checking
+ // // the combination of the error code and the error description,
+ // // since there is no special error code for duplicate messages.
+ // if (
+ // parsedError.code === 4 &&
+ // error instanceof Error &&
+ // error.message.includes('already exists')
+ // ) {
+ // updateMessage({
+ // ...localMessage,
+ // status: 'received',
+ // });
+ // } else {
+ // updateMessage({
+ // ...localMessage,
+ // error: parsedError,
+ // status: 'failed',
+ // });
+ //
+ // thread?.upsertReplyLocally({
+ // message: {
+ // ...localMessage,
+ // error: parsedError,
+ // status: 'failed',
+ // },
+ // });
+ // }
+ // }
+ // };
+
+ // const sendMessage = async ({
+ // localMessage,
+ // message,
+ // options,
+ // }: {
+ // localMessage: LocalMessage;
+ // message: Message;
+ // options?: SendMessageOptions;
+ // }) => {
+ // channel.state.filterErrorMessages();
+ //
+ // thread?.upsertReplyLocally({
+ // message: localMessage,
+ // });
+ //
+ // updateMessage(localMessage);
+ //
+ // await doSendMessage({ localMessage, message, options });
+ // };
+
+ // const retrySendMessage = async (localMessage: LocalMessage) => {
+ // updateMessage({
+ // ...localMessage,
+ // error: undefined,
+ // status: 'sending',
+ // });
+ //
+ // await doSendMessage({
+ // localMessage,
+ // message: localMessageToNewMessagePayload(localMessage),
+ // });
+ // };
const removeMessage = (message: LocalMessage) => {
channel.state.removeMessage(message);
- dispatch({
- channel,
- parentId: state.thread && message.parent_id,
- type: 'copyMessagesFromChannel',
- });
+ // dispatch({
+ // channel,
+ // parentId: state.thread && message.parent_id,
+ // type: 'copyMessagesFromChannel',
+ // });
};
/** THREAD */
const openThread = (message: LocalMessage, event?: React.BaseSyntheticEvent) => {
event?.preventDefault();
- dispatch({ channel, message, type: 'openThread' });
+ // dispatch({ channel, message, type: 'openThread' });
};
const closeThread = (event?: React.BaseSyntheticEvent) => {
event?.preventDefault();
- dispatch({ type: 'closeThread' });
+ // dispatch({ type: 'closeThread' });
};
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const loadMoreThreadFinished = useCallback(
- debounce(
- (
- threadHasMore: boolean,
- threadMessages: Array>,
- ) => {
- dispatch({
- threadHasMore,
- threadMessages,
- type: 'loadMoreThreadFinished',
- });
- },
- 2000,
- { leading: true, trailing: true },
- ),
- [],
- );
-
- const loadMoreThread = async (limit: number = DEFAULT_THREAD_PAGE_SIZE) => {
- // FIXME: should prevent loading more, if state.thread.reply_count === channel.state.threads[parentID].length
- if (state.threadLoadingMore || !state.thread || !state.threadHasMore) return;
-
- dispatch({ type: 'startLoadingThread' });
- const parentId = state.thread.id;
-
- if (!parentId) {
- return dispatch({ type: 'closeThread' });
- }
-
- const oldMessages = channel.state.threads[parentId] || [];
- const oldestMessageId = oldMessages[0]?.id;
-
- try {
- const queryResponse = await channel.getReplies(parentId, {
- id_lt: oldestMessageId,
- limit,
- });
-
- const threadHasMoreMessages = hasMoreMessagesProbably(
- queryResponse.messages.length,
- limit,
- );
- const newThreadMessages = channel.state.threads[parentId] || [];
-
- // next set loadingMore to false so we can start asking for more data
- loadMoreThreadFinished(threadHasMoreMessages, newThreadMessages);
- } catch (e) {
- loadMoreThreadFinished(false, oldMessages);
- }
- };
+ // const loadMoreThreadFinished = useCallback(
+ // debounce(
+ // (
+ // threadHasMore: boolean,
+ // threadMessages: Array>,
+ // ) => {
+ // dispatch({
+ // threadHasMore,
+ // threadMessages,
+ // type: 'loadMoreThreadFinished',
+ // });
+ // },
+ // 2000,
+ // { leading: true, trailing: true },
+ // ),
+ // [],
+ // );
+
+ // const loadMoreThread = async (limit: number = DEFAULT_THREAD_PAGE_SIZE) => {
+ // // FIXME: should prevent loading more, if state.thread.reply_count === channel.state.threads[parentID].length
+ // if (state.threadLoadingMore || !state.thread || !state.threadHasMore) return;
+ //
+ // dispatch({ type: 'startLoadingThread' });
+ // const parentId = state.thread.id;
+ //
+ // if (!parentId) {
+ // return dispatch({ type: 'closeThread' });
+ // }
+ //
+ // const oldMessages = channel.state.threads[parentId] || [];
+ // const oldestMessageId = oldMessages[0]?.id;
+ //
+ // try {
+ // const queryResponse = await channel.getReplies(parentId, {
+ // id_lt: oldestMessageId,
+ // limit,
+ // });
+ //
+ // const threadHasMoreMessages = hasMoreMessagesProbably(
+ // queryResponse.messages.length,
+ // limit,
+ // );
+ // const newThreadMessages = channel.state.threads[parentId] || [];
+ //
+ // // next set loadingMore to false so we can start asking for more data
+ // loadMoreThreadFinished(threadHasMoreMessages, newThreadMessages);
+ // } catch (e) {
+ // loadMoreThreadFinished(false, oldMessages);
+ // }
+ // };
const onMentionsHoverOrClick = useMentionsHandlers(onMentionsHover, onMentionsClick);
- const editMessage = useEditMessageHandler(doUpdateMessageRequest);
+ // const editMessage = useEditMessageHandler(doUpdateMessageRequest);
- const { typing, ...restState } = state;
+ // const { typing, ...restState } = state;
const channelStateContextValue = useCreateChannelStateContext({
- ...restState,
+ // ...restState,
channel,
channelCapabilitiesArray,
channelConfig,
- channelUnreadUiState,
+ // channelUnreadUiState,
giphyVersion: props.giphyVersion || 'fixed_height',
imageAttachmentSizeHandler:
props.imageAttachmentSizeHandler || getImageAttachmentConfiguration,
@@ -1162,7 +1161,7 @@ const ChannelInner = (
shouldGenerateVideoThumbnail: props.shouldGenerateVideoThumbnail || true,
videoAttachmentSizeHandler:
props.videoAttachmentSizeHandler || getVideoAttachmentConfiguration,
- watcher_count: state.watcherCount,
+ // watcher_count: state.watcherCount,
});
const channelActionContextValue: ChannelActionContextValue = useMemo(
@@ -1170,22 +1169,22 @@ const ChannelInner = (
addNotification,
closeThread,
deleteMessage,
- dispatch,
- editMessage,
- jumpToFirstUnreadMessage,
- jumpToLatestMessage,
- jumpToMessage,
- loadMore,
- loadMoreNewer,
- loadMoreThread,
+ // dispatch,
+ // editMessage,
+ // jumpToFirstUnreadMessage,
+ // jumpToLatestMessage,
+ // jumpToMessage,
+ // loadMore,
+ // loadMoreNewer,
+ // loadMoreThread,
markRead,
onMentionsClick: onMentionsHoverOrClick,
onMentionsHover: onMentionsHoverOrClick,
openThread,
removeMessage,
- retrySendMessage,
- sendMessage,
- setChannelUnreadUiState,
+ // retrySendMessage,
+ // sendMessage,
+ // setChannelUnreadUiState,
skipMessageDataMemoization,
updateMessage,
}),
@@ -1193,13 +1192,13 @@ const ChannelInner = (
[
channel.cid,
deleteMessage,
- loadMore,
- loadMoreNewer,
+ // loadMore,
+ // loadMoreNewer,
markRead,
- jumpToFirstUnreadMessage,
- jumpToMessage,
- jumpToLatestMessage,
- setChannelUnreadUiState,
+ // jumpToFirstUnreadMessage,
+ // jumpToMessage,
+ // jumpToLatestMessage,
+ // setChannelUnreadUiState,
],
);
@@ -1346,25 +1345,25 @@ const ChannelInner = (
],
);
- const typingContextValue = useCreateTypingContext({
- typing,
- });
-
- if (state.error) {
- return (
-
-
-
- );
- }
-
- if (state.loading) {
- return (
-
-
-
- );
- }
+ // const typingContextValue = useCreateTypingContext({
+ // typing,
+ // });
+
+ // if (state.error) {
+ // return (
+ //
+ //
+ //
+ // );
+ // }
+
+ // if (state.loading) {
+ // return (
+ //
+ //
+ //
+ // );
+ // }
if (!channel.watch) {
return (
@@ -1379,11 +1378,11 @@ const ChannelInner = (
-
-
- {children}
-
-
+ {/**/}
+
+ {children}
+
+ {/**/}
diff --git a/src/components/Channel/channelState.ts b/src/components/Channel/channelState.ts
index 597e1ca36b..2bccac8310 100644
--- a/src/components/Channel/channelState.ts
+++ b/src/components/Channel/channelState.ts
@@ -272,17 +272,24 @@ export const initialState = {
hasMoreNewer: false,
loading: true,
loadingMore: false,
+ // todo: add reactive state to Channel class
members: {},
messages: [],
+ // todo: add reactive state to Channel class
pinnedMessages: [],
+ // todo: add reactive state to Channel class
read: {},
+ // todo: could be moved as a prop to MessageList / VML
suppressAutoscroll: false,
thread: null,
threadHasMore: true,
threadLoadingMore: false,
threadMessages: [],
+ // todo: could be moved as a prop to MessageList / VML
threadSuppressAutoscroll: false,
+ // todo: add reactive state to Channel class
typing: {},
+ // todo: add reactive state to Channel class
watcherCount: 0,
watchers: {},
};
diff --git a/src/components/Channel/hooks/useCreateChannelStateContext.ts b/src/components/Channel/hooks/useCreateChannelStateContext.ts
index 3986592791..e08b316976 100644
--- a/src/components/Channel/hooks/useCreateChannelStateContext.ts
+++ b/src/components/Channel/hooks/useCreateChannelStateContext.ts
@@ -14,49 +14,49 @@ export const useCreateChannelStateContext = (
channel,
channelCapabilitiesArray = [],
channelConfig,
- channelUnreadUiState,
- error,
+ // channelUnreadUiState,
+ // error,
giphyVersion,
- hasMore,
- hasMoreNewer,
- highlightedMessageId,
+ // hasMore,
+ // hasMoreNewer,
+ // highlightedMessageId,
imageAttachmentSizeHandler,
- loading,
- loadingMore,
- members,
- messages = [],
+ // loading,
+ // loadingMore,
+ // members,
+ // messages = [],
mutes,
notifications,
- pinnedMessages,
- read = {},
+ // pinnedMessages,
+ // read = {},
shouldGenerateVideoThumbnail,
- skipMessageDataMemoization,
- suppressAutoscroll,
- thread,
- threadHasMore,
- threadLoadingMore,
- threadMessages = [],
+ // skipMessageDataMemoization,
+ // suppressAutoscroll,
+ // thread,
+ // threadHasMore,
+ // threadLoadingMore,
+ // threadMessages = [],
videoAttachmentSizeHandler,
- watcher_count,
- watcherCount,
- watchers,
+ // watcher_count,
+ // watcherCount,
+ // watchers,
} = value;
- const channelId = channel.cid;
- const lastRead = channel.initialized && channel.lastRead()?.getTime();
- const membersLength = Object.keys(members || []).length;
- const notificationsLength = notifications.length;
- const readUsers = Object.values(read);
- const readUsersLength = readUsers.length;
- const readUsersLastReadDateStrings: string[] = [];
- for (const { last_read } of readUsers) {
- if (!lastRead) continue;
- readUsersLastReadDateStrings.push(last_read?.toISOString());
- }
- const readUsersLastReads = readUsersLastReadDateStrings.join();
- const threadMessagesLength = threadMessages?.length;
+ // const channelId = channel.cid;
+ // const lastRead = channel.initialized && channel.lastRead()?.getTime();
+ // const membersLength = Object.keys(members || []).length;
+ // const notificationsLength = notifications.length;
+ // const readUsers = Object.values(read);
+ // const readUsersLength = readUsers.length;
+ // const readUsersLastReadDateStrings: string[] = [];
+ // for (const { last_read } of readUsers) {
+ // if (!lastRead) continue;
+ // readUsersLastReadDateStrings.push(last_read?.toISOString());
+ // }
+ // const readUsersLastReads = readUsersLastReadDateStrings.join();
+ // const threadMessagesLength = threadMessages?.length;
- const channelCapabilities: Record = {};
+ const channelCapabilities: Record = useMemo(() => ({}), []);
channelCapabilitiesArray.forEach((capability) => {
channelCapabilities[capability] = true;
@@ -64,100 +64,107 @@ export const useCreateChannelStateContext = (
// FIXME: this is crazy - I could not find out why the messages were not getting updated when only message properties that are not part
// of this serialization has been changed. A great example of memoization gone wrong.
- const memoizedMessageData = skipMessageDataMemoization
- ? messages
- : messages
- .map(
- ({
- deleted_at,
- latest_reactions,
- pinned,
- reply_count,
- status,
- type,
- updated_at,
- user,
- }) =>
- `${type}${deleted_at}${
- latest_reactions ? latest_reactions.map(({ type }) => type).join() : ''
- }${pinned}${reply_count}${status}${
- updated_at && (isDayOrMoment(updated_at) || isDate(updated_at))
- ? updated_at.toISOString()
- : updated_at || ''
- }${user?.updated_at}`,
- )
- .join();
+ // const memoizedMessageData = skipMessageDataMemoization
+ // ? messages
+ // : messages
+ // .map(
+ // ({
+ // deleted_at,
+ // latest_reactions,
+ // pinned,
+ // reply_count,
+ // status,
+ // type,
+ // updated_at,
+ // user,
+ // }) =>
+ // `${type}${deleted_at}${
+ // latest_reactions ? latest_reactions.map(({ type }) => type).join() : ''
+ // }${pinned}${reply_count}${status}${
+ // updated_at && (isDayOrMoment(updated_at) || isDate(updated_at))
+ // ? updated_at.toISOString()
+ // : updated_at || ''
+ // }${user?.updated_at}`,
+ // )
+ // .join();
- const memoizedThreadMessageData = threadMessages
- .map(
- ({ deleted_at, latest_reactions, pinned, status, updated_at, user }) =>
- `${deleted_at}${
- latest_reactions ? latest_reactions.map(({ type }) => type).join() : ''
- }${pinned}${status}${
- updated_at && (isDayOrMoment(updated_at) || isDate(updated_at))
- ? updated_at.toISOString()
- : updated_at || ''
- }${user?.updated_at}`,
- )
- .join();
+ // const memoizedThreadMessageData = threadMessages
+ // .map(
+ // ({ deleted_at, latest_reactions, pinned, status, updated_at, user }) =>
+ // `${deleted_at}${
+ // latest_reactions ? latest_reactions.map(({ type }) => type).join() : ''
+ // }${pinned}${status}${
+ // updated_at && (isDayOrMoment(updated_at) || isDate(updated_at))
+ // ? updated_at.toISOString()
+ // : updated_at || ''
+ // }${user?.updated_at}`,
+ // )
+ // .join();
const channelStateContext: ChannelStateContextValue = useMemo(
() => ({
channel,
channelCapabilities,
channelConfig,
- channelUnreadUiState,
- error,
+ // channelUnreadUiState,
+ // error,
giphyVersion,
- hasMore,
- hasMoreNewer,
- highlightedMessageId,
+ // hasMore,
+ // hasMoreNewer,
+ // highlightedMessageId,
imageAttachmentSizeHandler,
- loading,
- loadingMore,
- members,
- messages,
+ // loading,
+ // loadingMore,
+ // members,
+ // messages,
mutes,
notifications,
- pinnedMessages,
- read,
+ // pinnedMessages,
+ // read,
shouldGenerateVideoThumbnail,
- suppressAutoscroll,
- thread,
- threadHasMore,
- threadLoadingMore,
- threadMessages,
+ // suppressAutoscroll,
+ // thread,
+ // threadHasMore,
+ // threadLoadingMore,
+ // threadMessages,
videoAttachmentSizeHandler,
- watcher_count,
- watcherCount,
- watchers,
+ // watcher_count,
+ // watcherCount,
+ // watchers,
}),
- // eslint-disable-next-line react-hooks/exhaustive-deps
[
- channel.data?.name, // otherwise ChannelHeader will not be updated
- channelId,
- channelUnreadUiState,
- error,
- hasMore,
- hasMoreNewer,
- highlightedMessageId,
- lastRead,
- loading,
- loadingMore,
- membersLength,
- memoizedMessageData,
- memoizedThreadMessageData,
- notificationsLength,
- readUsersLength,
- readUsersLastReads,
+ channel,
+ // channel.data?.name, // otherwise ChannelHeader will not be updated
+ channelCapabilities,
+ channelConfig,
+ // channelId,
+ // channelUnreadUiState,
+ giphyVersion,
+ // error,
+ // hasMore,
+ // hasMoreNewer,
+ // highlightedMessageId,
+ imageAttachmentSizeHandler,
+ // lastRead,
+ // loading,
+ // loadingMore,
+ // membersLength,
+ // memoizedMessageData,
+ // memoizedThreadMessageData,
+ mutes,
+ notifications,
+ // notificationsLength,
+ // readUsersLength,
+ // readUsersLastReads,
shouldGenerateVideoThumbnail,
- skipMessageDataMemoization,
- suppressAutoscroll,
- thread,
- threadHasMore,
- threadLoadingMore,
- threadMessagesLength,
- watcherCount,
+ // skipMessageDataMemoization,
+ // suppressAutoscroll,
+ // thread,
+ // threadHasMore,
+ // threadLoadingMore,
+ // threadMessagesLength,
+ videoAttachmentSizeHandler,
+ // watcherCount,
],
);
diff --git a/src/components/ChannelHeader/ChannelHeader.tsx b/src/components/ChannelHeader/ChannelHeader.tsx
index 802478352e..ceb829aebf 100644
--- a/src/components/ChannelHeader/ChannelHeader.tsx
+++ b/src/components/ChannelHeader/ChannelHeader.tsx
@@ -33,7 +33,7 @@ export const ChannelHeader = (props: ChannelHeaderProps) => {
title: overrideTitle,
} = props;
- const { channel, watcher_count } = useChannelStateContext('ChannelHeader');
+ const { channel } = useChannelStateContext('ChannelHeader');
const { openMobileNav } = useChatContext('ChannelHeader');
const { t } = useTranslationContext('ChannelHeader');
const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({
@@ -76,7 +76,8 @@ export const ChannelHeader = (props: ChannelHeaderProps) => {
,{' '}
>
)}
- {t('{{ watcherCount }} online', { watcherCount: watcher_count })}
+ {/*todo: get the watcher count from LLC reactive state */}
+ {/*{t('{{ watcherCount }} online', { watcherCount: watcher_count })}*/}
diff --git a/src/components/ChatView/ChatView.tsx b/src/components/ChatView/ChatView.tsx
index 014731c87c..e080c30d17 100644
--- a/src/components/ChatView/ChatView.tsx
+++ b/src/components/ChatView/ChatView.tsx
@@ -10,7 +10,7 @@ import type { PropsWithChildren } from 'react';
import type { Thread, ThreadManagerState } from 'stream-chat';
import clsx from 'clsx';
-type ChatView = 'channels' | 'threads';
+type ChatView = 'channels' | 'threads' | (string & {});
type ChatViewContextValue = {
activeChatView: ChatView;
@@ -37,6 +37,7 @@ export const ChatView = ({ children }: PropsWithChildren) => {
);
};
+// todo: move channel list orchestrator here
const ChannelsView = ({ children }: PropsWithChildren) => {
const { activeChatView } = useContext(ChatViewContext);
diff --git a/src/components/InfiniteScrollPaginator/InfiniteScrollPaginator.tsx b/src/components/InfiniteScrollPaginator/InfiniteScrollPaginator.tsx
index 7457a1b69c..4118d5469f 100644
--- a/src/components/InfiniteScrollPaginator/InfiniteScrollPaginator.tsx
+++ b/src/components/InfiniteScrollPaginator/InfiniteScrollPaginator.tsx
@@ -1,6 +1,7 @@
import clsx from 'clsx';
import debounce from 'lodash.debounce';
import type { PropsWithChildren } from 'react';
+import { forwardRef } from 'react';
import React, { useEffect, useMemo, useRef } from 'react';
import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD } from '../../constants/limits';
@@ -14,7 +15,8 @@ const mousewheelListener = (event: Event) => {
}
};
-export type InfiniteScrollPaginatorProps = React.ComponentProps<'div'> & {
+type InfiniteScrollPaginatorOwnProps = {
+ element?: React.ElementType;
listenToScroll?: (
distanceFromBottom: number,
distanceFromTop: number,
@@ -28,12 +30,39 @@ export type InfiniteScrollPaginatorProps = React.ComponentProps<'div'> & {
useCapture?: boolean;
};
-export const InfiniteScrollPaginator = (
- props: PropsWithChildren,
-) => {
+// helper: get the right ref type for a given element/component
+type PolymorphicRef = React.ComponentPropsWithRef['ref'];
+
+// polymorphic props, defaulting to 'div'
+export type InfiniteScrollPaginatorProps =
+ PropsWithChildren<
+ InfiniteScrollPaginatorOwnProps & {
+ element?: C;
+ } & Omit<
+ React.ComponentPropsWithRef,
+ keyof InfiniteScrollPaginatorOwnProps | 'element'
+ >
+ >;
+
+type InfiniteScrollPaginatorComponent = (
+ props: InfiniteScrollPaginatorProps & {
+ ref?: PolymorphicRef;
+ },
+) => React.ReactNode;
+
+const renderPolymorphic = (
+ Comp: C,
+ props: React.ComponentPropsWithRef & { ref?: PolymorphicRef },
+ children?: React.ReactNode,
+) => React.createElement(Comp, props, children);
+
+export const InfiniteScrollPaginator = forwardRef(function InfiniteScrollPaginator<
+ E extends React.ElementType = 'div',
+>(props: InfiniteScrollPaginatorProps, ref: React.ForwardedRef) {
const {
children,
className,
+ element: Component = 'div' as E,
listenToScroll,
loadNextDebounceMs = 500,
loadNextOnScrollToBottom,
@@ -43,7 +72,7 @@ export const InfiniteScrollPaginator = (
...componentProps
} = props;
- const rootRef = useRef(null);
+ const rootRef = useRef(null);
const childRef = useRef(null);
const scrollListener = useMemo(
@@ -114,15 +143,24 @@ export const InfiniteScrollPaginator = (
};
}, [useCapture]);
- return (
-
+ return renderPolymorphic(
+ Component as E,
+ {
+ ...(componentProps as React.ComponentPropsWithRef),
+ className: clsx('str-chat__infinite-scroll-paginator', className),
+ ref: (node: React.ComponentRef | null) => {
+ if (typeof ref === 'function') {
+ ref(node);
+ } else if (ref && 'current' in ref) {
+ (ref as React.RefObject | null>).current = node;
+ }
+ rootRef.current = node && node instanceof HTMLElement ? node : null;
+ },
+ },
+ React.createElement(
+ 'div',
+ { className: 'str-chat__infinite-scroll-paginator__content', ref: childRef },
+ children,
+ ),
);
-};
+}) as InfiniteScrollPaginatorComponent;
diff --git a/src/components/MediaRecorder/hooks/useMediaRecorder.ts b/src/components/MediaRecorder/hooks/useMediaRecorder.ts
index 6438dc2b35..fd85714b78 100644
--- a/src/components/MediaRecorder/hooks/useMediaRecorder.ts
+++ b/src/components/MediaRecorder/hooks/useMediaRecorder.ts
@@ -6,6 +6,7 @@ import { useMessageComposer } from '../../MessageInput';
import type { LocalVoiceRecordingAttachment } from 'stream-chat';
import type { CustomAudioRecordingConfig, MediaRecordingState } from '../classes';
import type { MessageInputContextValue } from '../../../context';
+import { useSendMessageFn } from '../../MessageInput/hooks/useSendMessageFn';
export type RecordingController = {
completeRecording: () => void;
@@ -17,7 +18,7 @@ export type RecordingController = {
type UseMediaRecorderParams = Pick<
MessageInputContextValue,
- 'asyncMessagesMultiSendEnabled' | 'handleSubmit'
+ 'asyncMessagesMultiSendEnabled'
> & {
enabled: boolean;
generateRecordingTitle?: (mimeType: string) => string;
@@ -28,15 +29,15 @@ export const useMediaRecorder = ({
asyncMessagesMultiSendEnabled,
enabled,
generateRecordingTitle,
- handleSubmit,
recordingConfig,
}: UseMediaRecorderParams): RecordingController => {
const { t } = useTranslationContext('useMediaRecorder');
const messageComposer = useMessageComposer();
+ const sendMessageFn = useSendMessageFn();
const [recording, setRecording] = useState();
const [recordingState, setRecordingState] = useState();
const [permissionState, setPermissionState] = useState();
- const [isScheduledForSubmit, scheduleForSubmit] = useState(false);
+ // const [isScheduledForSubmit, scheduleForSubmit] = useState(false);
const recorder = useMemo(
() =>
@@ -56,17 +57,18 @@ export const useMediaRecorder = ({
if (!recording) return;
await messageComposer.attachmentManager.uploadAttachment(recording);
if (!asyncMessagesMultiSendEnabled) {
- // FIXME: cannot call handleSubmit() directly as the function has stale reference to attachments
- scheduleForSubmit(true);
+ await sendMessageFn();
+ // // FIXME: cannot call handleSubmit() directly as the function has stale reference to attachments
+ // scheduleForSubmit(true);
}
recorder.cleanUp();
- }, [asyncMessagesMultiSendEnabled, messageComposer, recorder]);
+ }, [asyncMessagesMultiSendEnabled, messageComposer, recorder, sendMessageFn]);
- useEffect(() => {
- if (!isScheduledForSubmit) return;
- handleSubmit();
- scheduleForSubmit(false);
- }, [handleSubmit, isScheduledForSubmit]);
+ // useEffect(() => {
+ // if (!isScheduledForSubmit) return;
+ // handleSubmit();
+ // scheduleForSubmit(false);
+ // }, [handleSubmit, isScheduledForSubmit]);
useEffect(() => {
if (!recorder) return;
diff --git a/src/components/Message/Message.tsx b/src/components/Message/Message.tsx
index 39b0e3f039..1fcb184907 100644
--- a/src/components/Message/Message.tsx
+++ b/src/components/Message/Message.tsx
@@ -12,7 +12,6 @@ import {
usePinHandler,
useReactionHandler,
useReactionsFetcher,
- useRetryHandler,
useUserHandler,
useUserRole,
} from './hooks';
@@ -47,7 +46,6 @@ type MessageContextPropsToPick =
| 'handleOpenThread'
| 'handlePin'
| 'handleReaction'
- | 'handleRetry'
| 'mutes'
| 'onMentionsClickMessage'
| 'onMentionsHoverMessage'
@@ -74,7 +72,7 @@ const MessageWithContext = (props: MessageWithContextProps) => {
} = props;
const { client, isMessageAIGenerated } = useChatContext('Message');
- const { channelConfig, read } = useChannelStateContext('Message');
+ const { channel, channelConfig } = useChannelStateContext('Message');
const { Message: contextMessage } = useComponentContext('Message');
const actionsEnabled = message.type === 'regular' && message.status === 'received';
@@ -104,13 +102,13 @@ const MessageWithContext = (props: MessageWithContextProps) => {
!!(
!isMyMessage &&
client.user?.id &&
- read &&
- (!read[client.user.id] ||
+ channel.state.read &&
+ (!channel.state.read[client.user.id] ||
(message?.created_at &&
new Date(message.created_at).getTime() >
- read[client.user.id].last_read.getTime()))
+ channel.state.read[client.user.id].last_read.getTime()))
),
- [client, isMyMessage, message.created_at, read],
+ [client, isMyMessage, message.created_at, channel],
);
const messageActionsHandler = useCallback(
@@ -208,18 +206,16 @@ export const Message = (props: MessageProps) => {
openThread: propOpenThread,
pinPermissions,
reactionDetailsSort,
- retrySendMessage: propRetrySendMessage,
sortReactionDetails,
sortReactions,
} = props;
const { addNotification } = useChannelActionContext('Message');
- const { highlightedMessageId, mutes } = useChannelStateContext('Message');
+ const { mutes } = useChannelStateContext('Message');
const handleAction = useActionHandler(message);
const handleOpenThread = useOpenThreadHandler(message, propOpenThread);
const handleReaction = useReactionHandler(message);
- const handleRetry = useRetryHandler(propRetrySendMessage);
const userRoles = useUserRole(message, onlySenderCanEdit, disableQuotedMessages);
const handleFetchReactions = useReactionsFetcher(message, {
@@ -260,7 +256,7 @@ export const Message = (props: MessageProps) => {
notify: addNotification,
});
- const highlighted = highlightedMessageId === message.id;
+ // const highlighted = highlightedMessageId === message.id;
return (
{
handleOpenThread={handleOpenThread}
handlePin={handlePin}
handleReaction={handleReaction}
- handleRetry={handleRetry}
- highlighted={highlighted}
+ // highlighted={highlighted}
initialMessage={props.initialMessage}
lastOwnMessage={props.lastOwnMessage}
lastReceivedId={props.lastReceivedId}
diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx
index 9bf01d1666..106dcb8b16 100644
--- a/src/components/Message/MessageSimple.tsx
+++ b/src/components/Message/MessageSimple.tsx
@@ -14,7 +14,7 @@ import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMes
import { isDateSeparatorMessage } from '../MessageList';
import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator';
import { ReminderNotification as DefaultReminderNotification } from './ReminderNotification';
-import { useMessageReminder } from './hooks';
+import { useMessageReminder, useRetryHandler } from './hooks';
import {
areMessageUIPropsEqual,
isMessageBlocked,
@@ -50,7 +50,6 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => {
groupedByUser,
handleAction,
handleOpenThread,
- handleRetry,
highlighted,
isMessageAIGenerated,
isMyMessage,
@@ -65,6 +64,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => {
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false);
const reminder = useMessageReminder(message.id);
+ const handleRetry = useRetryHandler();
const {
Attachment = DefaultAttachment,
@@ -125,7 +125,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => {
let handleClick: (() => void) | undefined = undefined;
if (allowRetry) {
- handleClick = () => handleRetry(message);
+ handleClick = () => handleRetry({ localMessage: message });
} else if (isBounced) {
handleClick = () => setIsBounceDialogOpen(true);
} else if (isEdited) {
diff --git a/src/components/Message/QuotedMessage.tsx b/src/components/Message/QuotedMessage.tsx
index db2cf19087..96575de738 100644
--- a/src/components/Message/QuotedMessage.tsx
+++ b/src/components/Message/QuotedMessage.tsx
@@ -7,12 +7,12 @@ import { Avatar as DefaultAvatar } from '../Avatar';
import { Poll } from '../Poll';
import { useChatContext } from '../../context/ChatContext';
import { useComponentContext } from '../../context/ComponentContext';
+import type { MessageContextValue } from '../../context/MessageContext';
import { useMessageContext } from '../../context/MessageContext';
import { useTranslationContext } from '../../context/TranslationContext';
-import { useChannelActionContext } from '../../context/ChannelActionContext';
import { renderText as defaultRenderText } from './renderText';
-import type { MessageContextValue } from '../../context/MessageContext';
import { useActionHandler } from './';
+import { useMessagePaginator } from '../../hooks';
export type QuotedMessageProps = Pick;
@@ -26,9 +26,8 @@ export const QuotedMessage = ({ renderText: propsRenderText }: QuotedMessageProp
renderText: contextRenderText,
} = useMessageContext('QuotedMessage');
const { t, userLanguage } = useTranslationContext('QuotedMessage');
- const { jumpToMessage } = useChannelActionContext('QuotedMessage');
const actionHandler = useActionHandler(message);
-
+ const messagePaginator = useMessagePaginator();
const renderText = propsRenderText ?? contextRenderText ?? defaultRenderText;
const Avatar = ContextAvatar || DefaultAvatar;
@@ -65,7 +64,7 @@ export const QuotedMessage = ({ renderText: propsRenderText }: QuotedMessageProp
onClickCapture={(e) => {
e.stopPropagation();
e.preventDefault();
- jumpToMessage(quoted_message.id);
+ messagePaginator.jumpToMessage(quoted_message.id);
}}
>
{quoted_message.user && (
diff --git a/src/components/Message/hooks/useRetryHandler.ts b/src/components/Message/hooks/useRetryHandler.ts
index 45d7c33d36..71468ccee0 100644
--- a/src/components/Message/hooks/useRetryHandler.ts
+++ b/src/components/Message/hooks/useRetryHandler.ts
@@ -1,17 +1,20 @@
-import type { RetrySendMessage } from '../../../context/ChannelActionContext';
-import { useChannelActionContext } from '../../../context/ChannelActionContext';
+import { useThreadContext } from '../../Threads';
+import { useChannelStateContext } from '../../../context';
+import type { RetrySendMessageWithLocalUpdateParams } from 'stream-chat';
+import { useCallback } from 'react';
-export const useRetryHandler = (
- customRetrySendMessage?: RetrySendMessage,
-): RetrySendMessage => {
- const { retrySendMessage: contextRetrySendMessage } =
- useChannelActionContext('useRetryHandler');
+export type RetryHandler = (
+ params: RetrySendMessageWithLocalUpdateParams,
+) => Promise;
- const retrySendMessage = customRetrySendMessage || contextRetrySendMessage;
+export const useRetryHandler = (): RetryHandler => {
+ const { channel } = useChannelStateContext();
+ const thread = useThreadContext();
- return async (message) => {
- if (message) {
- await retrySendMessage(message);
- }
- };
+ return useCallback(
+ async (params: RetrySendMessageWithLocalUpdateParams) => {
+ await (thread ?? channel).retrySendMessageWithLocalUpdate(params);
+ },
+ [channel, thread],
+ );
};
diff --git a/src/components/Message/types.ts b/src/components/Message/types.ts
index c32669cb60..b14731f438 100644
--- a/src/components/Message/types.ts
+++ b/src/components/Message/types.ts
@@ -1,11 +1,9 @@
import type { TFunction } from 'i18next';
import type { ReactNode } from 'react';
-import type { ReactionSort, UserResponse } from 'stream-chat';
+import type { LocalMessage, ReactionSort, UserResponse } from 'stream-chat';
import type { PinPermissions, UserEventHandler } from './hooks';
import type { MessageActionsArray } from './utils';
-
-import type { LocalMessage } from 'stream-chat';
import type { GroupStyle } from '../MessageList/utils';
import type { MessageInputProps } from '../MessageInput/MessageInput';
import type { ReactionDetailsComparator, ReactionsComparator } from '../Reactions/types';
@@ -99,8 +97,9 @@ export type MessageProps = {
mentioned_users?: UserResponse[],
options?: RenderTextOptions,
) => ReactNode;
- /** Custom retry send message handler to override default in [ChannelActionContext](https://getstream.io/chat/docs/sdk/react/contexts/channel_action_context/) */
- retrySendMessage?: ChannelActionContextValue['retrySendMessage'];
+ // todo: document how to register custom CustomSendMessageRequestFn with Channel and Thread through StreamChat
+ // /** Custom retry send message handler to override default in [ChannelActionContext](https://getstream.io/chat/docs/sdk/react/contexts/channel_action_context/) */
+ // retrySendMessage?: CustomSendMessageRequestFn;
/** Keep track of read receipts for each message sent by the user. When disabled, only the last own message delivery / read status is rendered. */
returnAllReadData?: boolean;
/** Comparator function to sort the list of reacted users
diff --git a/src/components/MessageInput/EditMessageForm.tsx b/src/components/MessageInput/EditMessageForm.tsx
index 215b154d48..9d1416ff7d 100644
--- a/src/components/MessageInput/EditMessageForm.tsx
+++ b/src/components/MessageInput/EditMessageForm.tsx
@@ -5,10 +5,13 @@ import { Modal as DefaultModal } from '../Modal';
import {
useComponentContext,
useMessageContext,
- useMessageInputContext,
useTranslationContext,
} from '../../context';
-import { useMessageComposer, useMessageComposerHasSendableData } from './hooks';
+import {
+ useMessageComposer,
+ useMessageComposerHasSendableData,
+ useUpdateMessageFn,
+} from './hooks';
import type { MessageUIComponentProps } from '../Message';
@@ -30,34 +33,32 @@ const EditMessageFormSendButton = () => {
export const EditMessageForm = () => {
const { t } = useTranslationContext('EditMessageForm');
const messageComposer = useMessageComposer();
- const { clearEditingState, handleSubmit } = useMessageInputContext('EditMessageForm');
-
- const cancel = useCallback(() => {
- clearEditingState?.();
- messageComposer.restore();
- }, [clearEditingState, messageComposer]);
+ const updateMessage = useUpdateMessageFn();
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
- if (event.key === 'Escape') cancel();
+ if (event.key === 'Escape') messageComposer.restore();
};
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
- }, [cancel]);
+ }, [messageComposer]);
return (