Skip to content

Commit c19ea09

Browse files
committed
feat: support message draft management for main message list
1 parent f00ee41 commit c19ea09

22 files changed

+1088
-222
lines changed

src/components/Channel/Channel.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import React, {
1313
import debounce from 'lodash.debounce';
1414
import defaultsDeep from 'lodash.defaultsdeep';
1515
import throttle from 'lodash.throttle';
16-
import { nanoid } from 'nanoid';
1716
import clsx from 'clsx';
1817

1918
import { initialState, makeChannelReducer } from './channelState';
@@ -60,12 +59,18 @@ import {
6059
useChannelContainerClasses,
6160
useImageFlagEmojisOnWindowsClass,
6261
} from './hooks/useChannelContainerClasses';
63-
import { findInMsgSetByDate, findInMsgSetById, makeAddNotifications } from './utils';
62+
import {
63+
findInMsgSetByDate,
64+
findInMsgSetById,
65+
generateMessageId,
66+
makeAddNotifications,
67+
} from './utils';
6468
import { useThreadContext } from '../Threads';
6569
import { getChannel } from '../../utils';
6670

6771
import type {
6872
APIErrorResponse,
73+
Channel as StreamChannel,
6974
ChannelAPIResponse,
7075
ChannelMemberResponse,
7176
ChannelQueryOptions,
@@ -76,7 +81,6 @@ import type {
7681
Message,
7782
MessageResponse,
7883
SendMessageAPIResponse,
79-
Channel as StreamChannel,
8084
StreamChat,
8185
UpdatedMessage,
8286
UserResponse,
@@ -239,6 +243,8 @@ export type ChannelProps<
239243
markReadOnMount?: boolean;
240244
/** Maximum number of attachments allowed per message */
241245
maxNumberOfFiles?: number;
246+
/** Enables storing message drafts on the server. */
247+
messageDraftsEnabled?: boolean;
242248
/** Whether to allow multiple attachment uploads */
243249
multipleUploads?: boolean;
244250
/** Custom action handler function to run on click of an @mention in a message */
@@ -343,6 +349,7 @@ const ChannelInner = <
343349
LoadingIndicator = DefaultLoadingIndicator,
344350
markReadOnMount = true,
345351
maxNumberOfFiles,
352+
messageDraftsEnabled,
346353
multipleUploads = true,
347354
onMentionsClick,
348355
onMentionsHover,
@@ -368,13 +375,17 @@ const ChannelInner = <
368375
const thread = useThreadContext();
369376

370377
const [channelConfig, setChannelConfig] = useState(channel.getConfig());
378+
// FIXME: Create a proper notification service in the LLC.
371379
const [notifications, setNotifications] = useState<ChannelNotifications>([]);
372-
const [quotedMessage, setQuotedMessage] = useState<StreamMessage<StreamChatGenerics>>();
380+
const notificationTimeouts = useRef<Array<NodeJS.Timeout>>([]);
381+
382+
// FIXME: Move to LLC message list controller.
383+
const [quotedMessage, setQuotedMessage] = useState<
384+
StreamMessage<StreamChatGenerics> | undefined
385+
>(messageDraftsEnabled ? channel.state.messageDraft?.quoted_message : undefined);
373386
const [channelUnreadUiState, _setChannelUnreadUiState] =
374387
useState<ChannelUnreadUiState>();
375388

376-
const notificationTimeouts = useRef<Array<NodeJS.Timeout>>([]);
377-
378389
const channelReducer = useMemo(() => makeChannelReducer<StreamChatGenerics>(), []);
379390

380391
const [state, dispatch] = useReducer(
@@ -385,6 +396,7 @@ const ChannelInner = <
385396
...initialState,
386397
hasMore: channel.state.messagePagination.hasPrev,
387398
loading: !channel.initialized,
399+
messageDraft: channel.state.messageDraft,
388400
},
389401
);
390402
const jumpToMessageFromSearch = useSearchFocusedMessage();
@@ -1111,7 +1123,7 @@ const ChannelInner = <
11111123
attachments,
11121124
created_at: new Date(),
11131125
html: text,
1114-
id: customMessageData?.id ?? `${client.userID}-${nanoid()}`,
1126+
id: customMessageData?.id ?? generateMessageId({ client }),
11151127
mentioned_users,
11161128
parent_id: parent?.id,
11171129
reactions: [],
@@ -1255,6 +1267,7 @@ const ChannelInner = <
12551267
imageAttachmentSizeHandler:
12561268
props.imageAttachmentSizeHandler || getImageAttachmentConfiguration,
12571269
maxNumberOfFiles,
1270+
messageDraftsEnabled,
12581271
multipleUploads,
12591272
mutes,
12601273
notifications,

src/components/Channel/channelState.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export const makeChannelReducer =
125125
return {
126126
...state,
127127
members: { ...channel.state.members },
128+
messageDraft: channel.state.messageDraft,
128129
messages: [...channel.state.messages],
129130
pinnedMessages: [...channel.state.pinnedMessages],
130131
read: { ...channel.state.read },
@@ -140,6 +141,7 @@ export const makeChannelReducer =
140141
hasMore,
141142
loading: false,
142143
members: { ...channel.state.members },
144+
messageDraft: channel.state.messageDraft,
143145
messages: [...channel.state.messages],
144146
pinnedMessages: [...channel.state.pinnedMessages],
145147
read: { ...channel.state.read },

src/components/Channel/hooks/useCreateChannelStateContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export const useCreateChannelStateContext = <
3434
loadingMore,
3535
maxNumberOfFiles,
3636
members,
37+
messageDraft,
38+
messageDraftsEnabled,
3739
messages = [],
3840
multipleUploads,
3941
mutes,
@@ -129,6 +131,8 @@ export const useCreateChannelStateContext = <
129131
loadingMore,
130132
maxNumberOfFiles,
131133
members,
134+
messageDraft,
135+
messageDraftsEnabled,
132136
messages,
133137
multipleUploads,
134138
mutes,
@@ -166,6 +170,8 @@ export const useCreateChannelStateContext = <
166170
membersLength,
167171
memoizedMessageData,
168172
memoizedThreadMessageData,
173+
messageDraft,
174+
messageDraftsEnabled,
169175
notificationsLength,
170176
onLinkPreviewDismissed,
171177
quotedMessage,

src/components/Channel/utils.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { nanoid } from 'nanoid';
22
import type { Dispatch, SetStateAction } from 'react';
3-
import type { ChannelState, MessageResponse } from 'stream-chat';
3+
import type {
4+
ChannelState,
5+
DefaultGenerics,
6+
ExtendableGenerics,
7+
MessageResponse,
8+
StreamChat,
9+
} from 'stream-chat';
410
import type { ChannelNotifications } from '../../context/ChannelStateContext';
511
import type { DefaultStreamChatGenerics } from '../../types';
612

@@ -105,3 +111,11 @@ export const findInMsgSetByDate = <
105111
}
106112
return { index: -1 };
107113
};
114+
115+
export const generateMessageId = <
116+
StreamChatGenerics extends ExtendableGenerics = DefaultGenerics,
117+
>({
118+
client,
119+
}: {
120+
client: StreamChat<StreamChatGenerics>;
121+
}) => `${client.userID}-${nanoid()}`;

src/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const FileAttachmentPreview = <
3333
removeAttachments,
3434
}: FileAttachmentPreviewProps<StreamChatGenerics>) => {
3535
const { t } = useTranslationContext('FilePreview');
36+
const uploadState = attachment.localMetadata?.uploadState;
3637
return (
3738
<div
3839
className='str-chat__attachment-preview-file'
@@ -46,7 +47,7 @@ export const FileAttachmentPreview = <
4647
aria-label={t('aria/Remove attachment')}
4748
className='str-chat__attachment-preview-delete'
4849
data-testid='file-preview-item-delete-button'
49-
disabled={attachment.localMetadata?.uploadState === 'uploading'}
50+
disabled={uploadState === 'uploading'}
5051
onClick={() =>
5152
attachment.localMetadata?.id &&
5253
removeAttachments([attachment.localMetadata?.id])
@@ -55,7 +56,7 @@ export const FileAttachmentPreview = <
5556
<CloseIcon />
5657
</button>
5758

58-
{attachment.localMetadata?.uploadState === 'failed' && !!handleRetry && (
59+
{uploadState === 'failed' && !!handleRetry && (
5960
<button
6061
className='str-chat__attachment-preview-error str-chat__attachment-preview-error-file'
6162
data-testid='file-preview-item-retry-button'
@@ -69,7 +70,8 @@ export const FileAttachmentPreview = <
6970
<div className='str-chat__attachment-preview-file-name' title={attachment.title}>
7071
{attachment.title}
7172
</div>
72-
{attachment.localMetadata?.uploadState === 'finished' &&
73+
{/* undefined if loaded from a draft */}
74+
{(typeof uploadState === 'undefined' || uploadState === 'finished') &&
7375
!!attachment.asset_url && (
7476
<a
7577
aria-label={t('aria/Download attachment')}
@@ -83,9 +85,7 @@ export const FileAttachmentPreview = <
8385
<DownloadIcon />
8486
</a>
8587
)}
86-
{attachment.localMetadata?.uploadState === 'uploading' && (
87-
<LoadingIndicatorIcon size={17} />
88-
)}
88+
{uploadState === 'uploading' && <LoadingIndicatorIcon size={17} />}
8989
</div>
9090
</div>
9191
);

0 commit comments

Comments
 (0)