Skip to content

Commit

Permalink
feat: added a user-mention menu (#480)
Browse files Browse the repository at this point in the history
Co-authored-by: Sidharth Mohanty <[email protected]>
  • Loading branch information
Spiral-Memory and sidmohanty11 authored Mar 3, 2024
1 parent 79ee4cb commit da8b930
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 3 deletions.
20 changes: 20 additions & 0 deletions packages/api/src/EmbeddedChatApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,26 @@ export default class EmbeddedChatApi {
}
}

async getMentionedMessages() {
try {
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(
`${this.host}/api/v1/chat.getMentionedMessages?roomId=${this.rid}`,
{
headers: {
"Content-Type": "application/json",
"X-Auth-Token": authToken,
"X-User-Id": userId,
},
method: "GET",
}
);
return await response.json();
} catch (err) {
console.error(err);
}
}

async pinMessage(mid: string) {
try {
const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
Expand Down
16 changes: 15 additions & 1 deletion packages/react/src/components/ChatHeader/ChatHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
useSearchMessageStore,
useChannelStore,
useToastStore,
useThreadsMessageStore,
useMentionsStore,
} from '../../store';
import { DynamicHeader } from '../DynamicHeader';
import { Tooltip } from '../Tooltip';
Expand All @@ -18,7 +20,6 @@ import useComponentOverrides from '../../theme/useComponentOverrides';
import { Icon } from '../Icon';
import { ActionButton } from '../ActionButton';
import { Menu } from '../Menu';
import useThreadsMessageStore from '../../store/threadsMessageStore';
import { useToastBarDispatch } from '../../hooks/useToastBarDispatch';
import useFetchChatData from '../../hooks/useFetchChatData';

Expand Down Expand Up @@ -73,6 +74,7 @@ const ChatHeader = ({
const setShowAllThreads = useThreadsMessageStore(
(state) => state.setShowAllThreads
);
const setShowMentions = useMentionsStore((state) => state.setShowMentions);
const toastPosition = useToastStore((state) => state.position);

const handleGoBack = async () => {
Expand Down Expand Up @@ -141,6 +143,11 @@ const ChatHeader = ({
setShowSearch(false);
}, [setShowAllThreads, setShowSearch]);

const showMentions = useCallback(async () => {
setShowMentions(true);
setShowSearch(false);
}, [setShowMentions, setShowSearch]);

useEffect(() => {
const setMessageAllowed = async () => {
const permissionRes = await RCInstance.permissionInfo();
Expand Down Expand Up @@ -223,6 +230,12 @@ const ChatHeader = ({
label: 'Threads',
icon: 'thread',
},
{
id: 'mentions',
action: showMentions,
label: 'Mentions',
icon: 'at',
},
{
id: 'members',
action: showChannelMembers,
Expand Down Expand Up @@ -273,6 +286,7 @@ const ChatHeader = ({
moreOpts,
setFullScreen,
showAllThreads,
showMentions,
showChannelMembers,
showChannelinformation,
showPinnedMessage,
Expand Down
17 changes: 17 additions & 0 deletions packages/react/src/components/Icon/icons/At.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/react/src/components/Icon/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import PinFilled from './PinFilled';
import VideoRecorder from './VideoRecoder';
import DisabledRecorder from './DisableRecorder';
import Clipboard from './Clipboard';
import At from './At';

const icons = {
file: File,
Expand Down Expand Up @@ -82,6 +83,7 @@ const icons = {
'arrow-down': ArrowDown,
'pin-filled': PinFilled,
clipboard: Clipboard,
at: At,
};

export default icons;
5 changes: 5 additions & 0 deletions packages/react/src/components/Menu/Menu.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export const Menu = {
label: 'Threads',
icon: 'thread',
},
{
id: 'mentions',
label: 'Mentions',
icon: 'at',
},
{
id: 'members',
label: 'Members',
Expand Down
7 changes: 5 additions & 2 deletions packages/react/src/components/MessageList/MessageList.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import {
useSearchMessageStore,
useChannelStore,
useUserStore,
useMentionsStore,
useThreadsMessageStore,
} from '../../store';
import RoomMembers from '../RoomMembers/RoomMember';
import MessageReportWindow from '../ReportMessage/MessageReportWindow';
import isMessageSequential from '../../lib/isMessageSequential';
import SearchMessage from '../SearchMessage/SearchMessage';
import Roominfo from '../RoomInformation/RoomInformation';
import AllThreads from '../AllThreads/AllThreads';
import UserMentions from '../UserMentions/UserMentions';
import { Message } from '../Message';

import useThreadsMessageStore from '../../store/threadsMessageStore';

const MessageList = ({ messages }) => {
const showSearch = useSearchMessageStore((state) => state.showSearch);
const showChannelinfo = useChannelStore((state) => state.showChannelinfo);
Expand All @@ -29,6 +30,7 @@ const MessageList = ({ messages }) => {
const showAllThreads = useThreadsMessageStore(
(state) => state.showAllThreads
);
const showMentions = useMentionsStore((state) => state.showMentions);

const isMessageNewDay = (current, previous) =>
!previous || !isSameDay(new Date(current.ts), new Date(previous.ts));
Expand Down Expand Up @@ -59,6 +61,7 @@ const MessageList = ({ messages }) => {
{showSearch && <SearchMessage />}
{showChannelinfo && <Roominfo />}
{showAllThreads && <AllThreads />}
{showMentions && <UserMentions />}
</>
);
};
Expand Down
180 changes: 180 additions & 0 deletions packages/react/src/components/UserMentions/UserMentions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import React, { useState, useEffect } from 'react';
import { css } from '@emotion/react';
import { isSameDay, format } from 'date-fns';
import classes from './UserMentions.module.css';
import { Icon } from '../Icon';
import { Box } from '../Box';
import { Attachments } from '../Attachments';
import { ActionButton } from '../ActionButton';
import { useMessageStore, useUserStore, useMentionsStore } from '../../store';
import { MessageBody } from '../Message/MessageBody';
import { MessageMetrics } from '../Message/MessageMetrics';
import { useRCContext } from '../../context/RCInstance';
import { Markdown } from '../Markdown';
import { MessageDivider } from '../Message/MessageDivider';
import MessageAvatarContainer from '../Message/MessageAvatarContainer';
import MessageBodyContainer from '../Message/MessageBodyContainer';
import MessageHeader from '../Message/MessageHeader';

const MessageCss = css`
display: flex;
flex-direction: row;
align-items: flex-start;
padding-top: 0.5rem;
-webkit-padding-before: 0.5rem;
padding-block-start: 0.5rem;
padding-bottom: 0.25rem;
-webkit-padding-after: 0.25rem;
padding-block-end: 0.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
padding-inline: 1.25rem;
&:hover {
background: #f2f3f5;
}
`;

const UserMentions = () => {
const showAvatar = useUserStore((state) => state.showAvatar);
const setShowMentions = useMentionsStore((state) => state.setShowMentions);
const { RCInstance } = useRCContext();
const [mentionedMessages, setMentionedMessages] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);

const openThread = useMessageStore((state) => state.openThread);

const toggleShowMentions = () => {
setShowMentions(false);
};
const handleOpenThread = (msg) => () => {
openThread(msg);
toggleShowMentions(false);
};
const isMessageNewDay = (current, previous) =>
!previous || !isSameDay(new Date(current.ts), new Date(previous.ts));

useEffect(() => {
const fetchMentionedMsgs = async () => {
const response = await RCInstance.getMentionedMessages();
if (response && response.messages) {
setMentionedMessages(response.messages);
setIsLoaded(true);
}
};
fetchMentionedMsgs();
}, [RCInstance, setMentionedMessages]);

return (
<Box className={classes.component}>
<Box className={classes.wrapContainer}>
<Box style={{ padding: '16px' }}>
<Box
css={css`
display: flex;
`}
>
<h3 style={{ display: 'contents' }}>
<Icon
name="at"
size="1.25rem"
style={{ padding: '0px 20px 20px 0px' }}
/>
<Box
css={css`
width: 100%;
color: #4a4a4a;
`}
>
Mentions
</Box>
<ActionButton onClick={toggleShowMentions} ghost size="small">
<Icon name="cross" size="1.25rem" />
</ActionButton>
</h3>
</Box>
</Box>

{isLoaded && (
<Box
style={{
flex: '1',
overflow: 'auto',
display: 'flex',
flexDirection: 'column',
justifyContent:
mentionedMessages.length === 0 ? 'center' : 'initial',
alignItems: mentionedMessages.length === 0 ? 'center' : 'initial',
}}
>
{mentionedMessages.length === 0 ? (
<Box
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
color: '#4a4a4a',
}}
>
<Icon
name="magnifier"
size="3rem"
style={{ padding: '0.5rem' }}
/>
<span style={{ fontSize: '1.2rem', fontWeight: 'bold' }}>
No mentions found
</span>
</Box>
) : (
mentionedMessages.map((message, index, arr) => {
const newDay =
index === 0 || isMessageNewDay(message, arr[index - 1]);
return (
<React.Fragment key={message._id}>
{newDay ? (
<MessageDivider>
{format(new Date(message.ts), 'MMMM d, yyyy')}
</MessageDivider>
) : null}
<Box css={MessageCss}>
{showAvatar && (
<MessageAvatarContainer
message={message}
sequential={false}
isStarred={false}
/>
)}
<MessageBodyContainer>
<MessageHeader message={message} />

<MessageBody>
{message.attachments &&
message.attachments.length > 0 ? (
<>
<Markdown body={message} isReaction={false} />
<Attachments attachments={message.attachments} />
</>
) : (
<Markdown body={message} isReaction={false} />
)}
</MessageBody>

{!message.t && message.tcount && (
<MessageMetrics
message={message}
handleOpenThread={handleOpenThread}
/>
)}
</MessageBodyContainer>
</Box>
</React.Fragment>
);
})
)}
</Box>
)}
</Box>
</Box>
);
};

export default UserMentions;
22 changes: 22 additions & 0 deletions packages/react/src/components/UserMentions/UserMentions.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.component {
position: fixed;
right: 0;
top: 0;
width: 350px;
height: 100%;
overflow: hidden;
background-color: white;
box-shadow: -1px 0px 5px rgb(0 0 0 / 25%);
z-index: 100;
}
.wrapContainer {
height: 100%;
display: flex;
flex-direction: column;
}

@media (max-width: 550px) {
.component {
width: 100vw;
}
}
1 change: 1 addition & 0 deletions packages/react/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as useSearchMessageStore } from './searchMessageStore';
export { default as loginModalStore } from './loginmodalStore';
export { default as useChannelStore } from './channelStore';
export { default as useThreadsMessageStore } from './threadsMessageStore';
export { default as useMentionsStore } from './mentionsStore';
8 changes: 8 additions & 0 deletions packages/react/src/store/mentionsStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { create } from 'zustand';

const useMentionsStore = create((set) => ({
showMentions: false,
setShowMentions: (showMentions) => set(() => ({ showMentions })),
}));

export default useMentionsStore;

0 comments on commit da8b930

Please sign in to comment.