Skip to content

Commit a8683d4

Browse files
author
Sravan S
authored
feat: improve structure of message UI for copying (#476)
Before: * The words inside messages were kept in separate spans * Which would lead to unfavourable formatting when pasted After: * Export MessageProvider, a simple provider to avoid prop drilling into Messages Note - this is still in works, but the props will remain ``` export type MessageProviderProps = { children: React.ReactNode; message: BaseMessage; isByMe?: boolean; } import { MessageProvider, useMessageContext } from '@sendbird/uikit-react/Message/context' ``` Incase if you were using MessageComponents and see error message `useMessageContext must be used within a MessageProvider ` use: `<MessageProvider message={message}><CustomMessage /></MessageProvider>` * Implement a Provider/Module to improve customization of messages * This is still in progress, not exported yet * In the future, we can add information such as -> * Menu render options, edit, delete callbacks etc * This will improve the customizability and remove some prop drilling in Messages * Remove span between simple strings * Urls and Mentions will still be wrapped in spans(for formatting) * Apply new logic & components(TextFragment) to tokenize strings * Improve keys used in rendering inside message, * UUIDs are not the optimal way to improve rendering * Create a composite key with message updated at * Refactor usePaste hook to make mentions work ~ * Fix overflow of long strings * Deprecate `Word` and `convertWordToStringObj` fixes: https://sendbird.atlassian.net/browse/UIKIT-3400 https://sendbird.atlassian.net/browse/UIKIT-3561
1 parent 51ebeac commit a8683d4

File tree

43 files changed

+935
-399
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+935
-399
lines changed

.storybook/preview-head.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
const modalRoot = document.createElement('div');
99
modalRoot.setAttribute('id', 'sendbird-modal-root');
10+
modalRoot.style.position = 'fixed';
11+
modalRoot.style.zIndex = '99999999999';
1012
body.appendChild(modalRoot);
1113
});
1214
</script>

exports.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ export default {
102102
'MessageSearch/context': 'src/smart-components/MessageSearch/context/MessageSearchProvider.tsx',
103103
'MessageSearch/components/MessageSearchUI': 'src/smart-components/MessageSearch/components/MessageSearchUI/index.tsx',
104104

105+
// Message
106+
'Message/context': 'src/smart-components/Message/context/index.tsx',
107+
105108
// Thread
106109
Thread: 'src/smart-components/Thread/index.tsx',
107110
'Thread/context': 'src/smart-components/Thread/context/ThreadProvider.tsx',

scripts/index_d_ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,16 @@ declare module "SendbirdUIKitGlobal" {
938938
onCloseClick?: () => void;
939939
}
940940

941+
/**
942+
* Message
943+
*/
944+
export interface MessageProviderProps {
945+
message: BaseMessage;
946+
isByMe?: boolean;
947+
children?: React.ReactElement;
948+
}
949+
export type MessageProviderInterface = Exclude<MessageProviderProps, 'children'>;
950+
941951
/**
942952
* Thread
943953
*/
@@ -1558,6 +1568,15 @@ declare module '@sendbird/uikit-react/MessageSearch/components/MessageSearchUI'
15581568
export default MessageSearchUI;
15591569
}
15601570

1571+
/**
1572+
* Message
1573+
*/
1574+
declare module '@sendbird/uikit-react/Message/context' {
1575+
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
1576+
export const useMessageContext: () => SendbirdUIKitGlobal.MessageProviderInterface;
1577+
export const MessageProvider: React.FC<SendbirdUIKitGlobal.MessageProviderProps>;
1578+
}
1579+
15611580
/**
15621581
* Thread
15631582
*/

src/lib/hooks/__tests__/schedulerFactory.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { LoggerFactory } from '../../Logger';
66
jest.useFakeTimers();
77
jest.spyOn(global, 'setInterval');
88

9-
const logger = LoggerFactory('all');
9+
const logger = LoggerFactory('info');
1010

1111
describe('schedulerFactory', () => {
1212
it('should return a scheduler with push and clear methods', () => {

src/smart-components/Channel/components/MessageList/index.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import FrozenNotification from '../FrozenNotification';
1414
import { SCROLL_BUFFER } from '../../../../utils/consts';
1515
import { EveryMessage } from '../../../..';
1616
import useSendbirdStateContext from '../../../../hooks/useSendbirdStateContext';
17+
import { UserMessage } from '@sendbird/chat/message';
18+
import { MessageProvider } from '../../../Message/context/MessageProvider';
1719

1820
export interface MessageListProps {
1921
className?: string;
@@ -51,11 +53,11 @@ const MessageList: React.FC<MessageListProps> = ({
5153
loading,
5254
unreadSince,
5355
} = useChannelContext();
56+
const store = useSendbirdStateContext();
5457
const [scrollBottom, setScrollBottom] = useState(0);
5558
const allMessagesFiltered = (typeof filterMessageList === 'function')
5659
? allMessages.filter((filterMessageList as (message: EveryMessage) => boolean))
5760
: allMessages;
58-
const store = useSendbirdStateContext();
5961
const markAsReadScheduler = store.config.markAsReadScheduler;
6062

6163
const onScroll = (e) => {
@@ -165,17 +167,19 @@ const MessageList: React.FC<MessageListProps> = ({
165167
currentMessage: m,
166168
currentChannel: currentGroupChannel,
167169
});
170+
const isByMe = (m as UserMessage)?.sender?.userId === store?.config?.userId;
168171
return (
169-
<Message
170-
key={m?.messageId}
171-
handleScroll={handleScroll}
172-
renderMessage={renderMessage}
173-
message={m}
174-
hasSeparator={hasSeparator}
175-
chainTop={chainTop}
176-
chainBottom={chainBottom}
177-
renderCustomSeparator={renderCustomSeparator}
178-
/>
172+
<MessageProvider message={m} key={m?.messageId} isByMe={isByMe}>
173+
<Message
174+
handleScroll={handleScroll}
175+
renderMessage={renderMessage}
176+
message={m}
177+
hasSeparator={hasSeparator}
178+
chainTop={chainTop}
179+
chainBottom={chainBottom}
180+
renderCustomSeparator={renderCustomSeparator}
181+
/>
182+
</MessageProvider>
179183
);
180184
})}
181185
{/* show frozen notifications */}

src/smart-components/ChannelList/components/ChannelListUI/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import './channel-list-ui.scss';
22

3-
import React, { useState, useEffect } from 'react';
3+
import React, { useState } from 'react';
44
import type { GroupChannel, Member, SendbirdGroupChat } from '@sendbird/chat/groupChannel';
55
import type { User } from '@sendbird/chat';
66

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Message Module
2+
3+
When we first started UIKit, messages were a simple component that was used to display a file/text message.
4+
As we started to build more complex applications, we realized that we needed a more robust way to display messages.
5+
The Message module was created to address this need.
6+
7+
We plan to deprecate other message components in the future(UIKit@v4) and replace them with the Message module.
8+
9+
For now, we will keep the old message components and the new Message module in parallel. And try to add new components to the Message module.
10+
11+
Message
12+
-> UserMessage
13+
-> NotificationMessage / TemplateMessage
14+
-> FileMessage
15+
-> ThumbnailMessage(image/video)
16+
-> VoiceMessage
17+
-> FileMessage(others)
18+
-> AdminMessage
19+
20+
A UserMessage can have 3 components.
21+
-> Simple Text
22+
-> Mention
23+
-> OGMessage
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
import { UserMessage } from '@sendbird/chat/message';
3+
4+
import { TOKEN_TYPES, Token } from '../../utils/tokens/types'
5+
import { useMessageContext } from '../../context/MessageProvider';
6+
import { keyGenerator } from '../../utils/tokens/keyGenerator';
7+
import MentionLabel from '../../../../ui/MentionLabel';
8+
import { USER_MENTION_PREFIX } from '../../consts';
9+
import LinkLabel from '../../../../ui/LinkLabel';
10+
import { LabelTypography } from '../../../../ui//Label';
11+
12+
export type TextFragmentProps = {
13+
tokens: Token[];
14+
}
15+
16+
export default function TextFragment({
17+
tokens,
18+
}: TextFragmentProps): React.ReactElement {
19+
const messageStore = useMessageContext();
20+
21+
const message = messageStore?.message as UserMessage;
22+
const isByMe = messageStore?.isByMe;
23+
const { updatedAt, createdAt } = message;
24+
return (
25+
<>
26+
{
27+
tokens?.map((token, idx) => {
28+
const key = keyGenerator(createdAt, updatedAt, idx);
29+
switch (token.type) {
30+
case TOKEN_TYPES.mention:
31+
return (
32+
<span className="sendbird-word" key={key}>
33+
<MentionLabel
34+
mentionTemplate={USER_MENTION_PREFIX}
35+
mentionedUserId={token.userId}
36+
mentionedUserNickname={token.value}
37+
isByMe={isByMe}
38+
/>
39+
</span>
40+
);
41+
case TOKEN_TYPES.url:
42+
return (
43+
<span className='sendbird-word' key={key}>
44+
<LinkLabel
45+
className="sendbird-word__url"
46+
src={token.value}
47+
type={LabelTypography.BODY_1}
48+
>
49+
{token.value}
50+
</LinkLabel>
51+
</span>
52+
);
53+
case TOKEN_TYPES.string:
54+
default:
55+
return (
56+
<React.Fragment key={key}>{token.value}</React.Fragment>
57+
);
58+
}
59+
})
60+
}
61+
</>
62+
);
63+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const USER_MENTION_PREFIX = "@";
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// todo@v4.0.0: combine with the provider in core-ts, see:
2+
// https://github.com/sendbird/sendbird-uikit-core-ts
3+
// packages/react-uikit-message-template-view/src/context/MessageContextProvider.tsx
4+
import React from 'react';
5+
import { BaseMessage } from '@sendbird/chat/message';
6+
7+
export type MessageProviderProps = {
8+
children: React.ReactNode;
9+
message: BaseMessage;
10+
isByMe?: boolean;
11+
}
12+
13+
export type MessageProviderInterface = Exclude<MessageProviderProps, 'children'>;
14+
15+
const MessageContext = React.createContext(undefined);
16+
17+
const MessageProvider: React.FC<MessageProviderInterface> = (props: MessageProviderProps) => {
18+
const {
19+
children,
20+
message,
21+
isByMe = false,
22+
} = props;
23+
24+
return (
25+
<MessageContext.Provider value={{
26+
message,
27+
isByMe,
28+
}}>
29+
{children}
30+
</MessageContext.Provider>
31+
);
32+
}
33+
34+
const useMessageContext = (): MessageProviderInterface => {
35+
const value = React.useContext(MessageContext);
36+
if (value === undefined) {
37+
throw new Error('useMessageContext must be used within a MessageProvider');
38+
}
39+
return value;
40+
};
41+
42+
export {
43+
MessageProvider,
44+
useMessageContext,
45+
};

0 commit comments

Comments
 (0)