diff --git a/README.md b/README.md
index e6a0a6ed1..e5abf5c7f 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ _EmbeddedChat is a full-stack React component node module of the RocketChat appl
## Installation and Usage
-Installtion and usage documentation could be found here [EmbeddedChat installation and usage](packages/react/README.md)
+Installation and usage documentation could be found here [EmbeddedChat installation and usage](packages/react/README.md)
## Development
diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts
index 6d3cc6987..7e0ff5eca 100644
--- a/packages/api/src/EmbeddedChatApi.ts
+++ b/packages/api/src/EmbeddedChatApi.ts
@@ -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()) || {};
diff --git a/packages/react/src/components/Attachments/PinnedAttachment.js b/packages/react/src/components/Attachments/PinnedAttachment.js
index ae694b2a6..0255cb9ea 100644
--- a/packages/react/src/components/Attachments/PinnedAttachment.js
+++ b/packages/react/src/components/Attachments/PinnedAttachment.js
@@ -1,18 +1,46 @@
-import React from 'react';
+import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { Box } from '../Box';
+import { Avatar } from '../Avatar';
+import RCContext from '../../context/RCInstance';
-const PinnedAttachment = ({ attachment }) => (
-
- {attachment?.author_name}
- {attachment?.text}
-
-);
+const PinnedAttachment = ({ attachment }) => {
+ const { RCInstance } = useContext(RCContext);
+ const getUserAvatarUrl = (authorIcon) => {
+ const host = RCInstance.getHost();
+ const URL = `${host}${authorIcon}`;
+ return URL;
+ };
+ return (
+
+
+
+ {attachment?.author_name}
+
+
+ {attachment?.text}
+
+
+ );
+};
export default PinnedAttachment;
diff --git a/packages/react/src/components/ChatHeader/ChatHeader.js b/packages/react/src/components/ChatHeader/ChatHeader.js
index 0ec1a8183..08cfdc8bd 100644
--- a/packages/react/src/components/ChatHeader/ChatHeader.js
+++ b/packages/react/src/components/ChatHeader/ChatHeader.js
@@ -10,6 +10,8 @@ import {
useSearchMessageStore,
useChannelStore,
useToastStore,
+ useThreadsMessageStore,
+ useMentionsStore,
} from '../../store';
import { DynamicHeader } from '../DynamicHeader';
import { Tooltip } from '../Tooltip';
@@ -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';
@@ -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 () => {
@@ -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();
@@ -223,6 +230,12 @@ const ChatHeader = ({
label: 'Threads',
icon: 'thread',
},
+ {
+ id: 'mentions',
+ action: showMentions,
+ label: 'Mentions',
+ icon: 'at',
+ },
{
id: 'members',
action: showChannelMembers,
@@ -273,6 +286,7 @@ const ChatHeader = ({
moreOpts,
setFullScreen,
showAllThreads,
+ showMentions,
showChannelMembers,
showChannelinformation,
showPinnedMessage,
diff --git a/packages/react/src/components/ChatInput/ChatInput.js b/packages/react/src/components/ChatInput/ChatInput.js
index 026b152f0..7852f90cb 100644
--- a/packages/react/src/components/ChatInput/ChatInput.js
+++ b/packages/react/src/components/ChatInput/ChatInput.js
@@ -8,11 +8,11 @@ import {
useMessageStore,
loginModalStore,
useChannelStore,
+ useMemberStore,
} from '../../store';
import ChatInputFormattingToolbar from './ChatInputFormattingToolbar';
import useAttachmentWindowStore from '../../store/attachmentwindow';
import MembersList from '../Mentions/MembersList';
-import mentionmemberStore from '../../store/mentionmemberStore';
import { searchToMentionUser } from '../../lib/searchToMentionUser';
import TypingUsers from '../TypingUsers';
import createPendingMessage from '../../lib/createPendingMessage';
@@ -22,6 +22,7 @@ import { Box } from '../Box';
import { Icon } from '../Icon';
import { CommandsList } from '../CommandList';
import { ActionButton } from '../ActionButton';
+import { Divider } from '../Divider';
import useComponentOverrides from '../../theme/useComponentOverrides';
import { useToastBarDispatch } from '../../hooks/useToastBarDispatch';
@@ -45,12 +46,23 @@ const ChatInput = ({ scrollToBottom }) => {
(state) => state.setIsUserAuthenticated
);
+ const isChannelPrivate = useChannelStore((state) => state.isChannelPrivate);
+
+ const members = useMemberStore((state) => state.members);
+ const setMembersHandler = useMemberStore((state) => state.setMembersHandler);
+
useEffect(() => {
RCInstance.auth.onAuthChange((user) => {
if (user) {
RCInstance.getCommandsList()
.then((data) => setCommands(data.commands || []))
.catch(console.error);
+
+ RCInstance.getChannelMembers(isChannelPrivate)
+ .then((channelMembers) =>
+ setMembersHandler(channelMembers.members || [])
+ )
+ .catch(console.error);
}
});
}, [RCInstance]);
@@ -68,28 +80,19 @@ const ChatInput = ({ scrollToBottom }) => {
const inputRef = useRef(null);
const typingRef = useRef();
- const messageRef = useRef();
+ const messageRef = useRef(null);
const [disableButton, setDisableButton] = useState(true);
- const roomMembers = mentionmemberStore((state) => state.roomMembers);
- const setRoomMembers = mentionmemberStore((state) => state.setRoomMembers);
-
const [filteredMembers, setFilteredMembers] = useState([]);
const [mentionIndex, setmentionIndex] = useState(-1);
const [startReading, setStartReading] = useState(false);
- const showMembersList = mentionmemberStore((state) => state.showMembersList);
- const setshowMembersList = mentionmemberStore(
- (state) => state.toggleShowMembers
- );
+ const [showMembersList, setshowMembersList] = useState(false);
+
const setIsLoginModalOpen = loginModalStore(
(state) => state.setIsLoginModalOpen
);
- const isChannelPrivate = useChannelStore((state) => state.isChannelPrivate);
- const setIsChannelPrivate = useChannelStore(
- (state) => state.setIsChannelPrivate
- );
const {
editMessage,
@@ -143,7 +146,7 @@ const ChatInput = ({ scrollToBottom }) => {
};
const sendMessage = async () => {
- scrollToBottom();
+ messageRef.current.focus();
messageRef.current.style.height = '44px';
const message = messageRef.current.value.trim();
if (!message.length || !isUserAuthenticated) {
@@ -207,6 +210,8 @@ const ChatInput = ({ scrollToBottom }) => {
setDisableButton(true);
setEditMessage({});
}
+
+ scrollToBottom();
};
const sendAttachment = (event) => {
@@ -217,16 +222,6 @@ const ChatInput = ({ scrollToBottom }) => {
toggle();
setData(event.target.files[0]);
};
- const getAllChannelMembers = useCallback(async () => {
- try {
- const channelMembers = await RCInstance.getChannelMembers(
- isChannelPrivate
- );
- setRoomMembers(channelMembers.members);
- } catch (e) {
- console.error(e);
- }
- }, [RCInstance, setRoomMembers, isChannelPrivate]);
useEffect(() => {
if (editMessage.msg) {
@@ -235,9 +230,6 @@ const ChatInput = ({ scrollToBottom }) => {
messageRef.current.value = '';
}
}, [editMessage]);
- useEffect(() => {
- getAllChannelMembers();
- }, [getAllChannelMembers]);
const username = useUserStore((state) => state.username);
const timerRef = useRef();
@@ -285,6 +277,34 @@ const ChatInput = ({ scrollToBottom }) => {
}
}, []);
+ const handleMemberClick = (selectedItem) => {
+ setshowMembersList(false);
+
+ let insertionText;
+ if (selectedItem === 'all') {
+ insertionText = `${messageRef.current.value.substring(
+ 0,
+ messageRef.current.value.lastIndexOf('@')
+ )}@all `;
+ } else if (selectedItem === 'here') {
+ insertionText = `${messageRef.current.value.substring(
+ 0,
+ messageRef.current.value.lastIndexOf('@')
+ )}@here `;
+ } else {
+ insertionText = `${messageRef.current.value.substring(
+ 0,
+ messageRef.current.value.lastIndexOf('@')
+ )}@${selectedItem.username} `;
+ }
+
+ messageRef.current.value = insertionText;
+
+ const cursorPosition = insertionText.length;
+ messageRef.current.setSelectionRange(cursorPosition, cursorPosition);
+ messageRef.current.focus();
+ };
+
const showCommands = useCallback(
async (e) => {
const cursor = e.target.selectionStart;
@@ -320,7 +340,7 @@ const ChatInput = ({ scrollToBottom }) => {
}
searchToMentionUser(
messageRef.current.value,
- roomMembers,
+ members,
startReading,
setStartReading,
setFilteredMembers,
@@ -386,36 +406,41 @@ const ChatInput = ({ scrollToBottom }) => {
}
if (e.key === 'ArrowDown') {
+ e.preventDefault();
setmentionIndex(
mentionIndex + 1 >= filteredMembers.length + 2 ? 0 : mentionIndex + 1
);
}
if (e.key === 'ArrowUp') {
+ e.preventDefault();
setmentionIndex(
mentionIndex - 1 < 0 ? filteredMembers.length + 1 : mentionIndex - 1
);
- }
- if (showMembersList && e.key === 'Enter') {
- e.preventDefault();
- let selectedMember = null;
- if (mentionIndex === filteredMembers.length) selectedMember = 'all';
- else if (mentionIndex === filteredMembers.length + 1)
- selectedMember = 'everyone';
- else selectedMember = filteredMembers[mentionIndex].username;
- messageRef.current.value = `${messageRef.current.value.substring(
- 0,
- messageRef.current.value.lastIndexOf('@')
- )}@${selectedMember}`;
-
- setshowMembersList(false);
- setStartReading(false);
- setFilteredMembers([]);
- setmentionIndex(-1);
+ const lastIndexOfAt = messageRef.current.value.lastIndexOf('@');
+ const cursorPosition = lastIndexOfAt === -1 ? 0 : lastIndexOfAt + 1;
+ messageRef.current.setSelectionRange(cursorPosition, cursorPosition);
}
if (e.key === 'Enter') {
- sendTypingStop();
+ e.preventDefault();
+ if (showMembersList) {
+ let selectedMember = null;
+ if (mentionIndex === filteredMembers.length) selectedMember = 'all';
+ else if (mentionIndex === filteredMembers.length + 1)
+ selectedMember = 'here';
+ else selectedMember = filteredMembers[mentionIndex].username;
+
+ handleMemberClick(selectedMember);
+
+ setshowMembersList(false);
+ setStartReading(false);
+ setFilteredMembers([]);
+ setmentionIndex(-1);
+ } else {
+ sendTypingStop();
+ sendMessage();
+ }
}
};
return (
@@ -434,10 +459,14 @@ const ChatInput = ({ scrollToBottom }) => {
`}
>
{showMembersList ? (
-
+ <>
+
+
+ >
) : (
<>>
)}
diff --git a/packages/react/src/components/Icon/icons/At.js b/packages/react/src/components/Icon/icons/At.js
new file mode 100644
index 000000000..aeb9061fd
--- /dev/null
+++ b/packages/react/src/components/Icon/icons/At.js
@@ -0,0 +1,17 @@
+import React from 'react';
+
+const At = (props) => (
+
+);
+
+export default At;
diff --git a/packages/react/src/components/Icon/icons/Clipboard.js b/packages/react/src/components/Icon/icons/Clipboard.js
new file mode 100644
index 000000000..c7ac76e89
--- /dev/null
+++ b/packages/react/src/components/Icon/icons/Clipboard.js
@@ -0,0 +1,14 @@
+import React from 'react';
+
+const Clipboard = (props) => (
+
+);
+
+export default Clipboard;
diff --git a/packages/react/src/components/Icon/icons/index.js b/packages/react/src/components/Icon/icons/index.js
index 490e0982b..ac5095831 100644
--- a/packages/react/src/components/Icon/icons/index.js
+++ b/packages/react/src/components/Icon/icons/index.js
@@ -39,6 +39,9 @@ import PinFilled from './PinFilled';
import VideoRecorder from './VideoRecoder';
import DisabledRecorder from './DisableRecorder';
import Copy from './Copy';
+import Clipboard from './Clipboard';
+import At from './At';
+
const icons = {
file: File,
@@ -82,6 +85,8 @@ const icons = {
'arrow-down': ArrowDown,
'pin-filled': PinFilled,
copy: Copy,
+ clipboard: Clipboard,
+ at: At,
};
export default icons;
diff --git a/packages/react/src/components/Markup/elements/Mention.js b/packages/react/src/components/Markup/elements/Mention.js
index 720c2af0b..a0d4c140e 100644
--- a/packages/react/src/components/Markup/elements/Mention.js
+++ b/packages/react/src/components/Markup/elements/Mention.js
@@ -1,11 +1,37 @@
import React from 'react';
+import { css } from '@emotion/react';
import PropTypes from 'prop-types';
-import useMentionMemberStore from '../../../store/mentionmemberStore';
+import { useMemberStore, useUserStore } from '../../../store';
const Mention = ({ contents }) => {
- const members = useMentionMemberStore((state) => state.roomMembers || []);
+ const members = useMemberStore((state) => state.members);
+ const username = useUserStore((state) => state.username);
+
+ const mentionStyles = css`
+ background-color: ${contents.value === 'all' || contents.value === 'here'
+ ? '#f38c39'
+ : contents.value === username
+ ? '#ec0d2a'
+ : '#e4e7ea'};
+ color: ${contents.value === 'all' || contents.value === 'here'
+ ? '#ffffff'
+ : contents.value === username
+ ? '#ffffff'
+ : '#2f343d'};
+ font-weight: bold;
+ cursor: pointer;
+ padding: 1.5px;
+ border-radius: 3px;
+
+ &:hover {
+ text-decoration: ${contents.value === 'all' || contents.value === 'here'
+ ? 'none'
+ : 'underline'};
+ }
+ `;
+
const hasMember = (user) => {
- if (user === 'all' || user === 'everyone') return true;
+ if (user === 'all' || user === 'here') return true;
let found = false;
Object.keys(members).forEach((ele) => {
if (members[ele].username === user) {
@@ -17,9 +43,7 @@ const Mention = ({ contents }) => {
return (
<>
{hasMember(contents.value) === true ? (
-
- {contents.value}
-
+ {contents.value}
) : (
`@${contents.value}`
)}
diff --git a/packages/react/src/components/Mentions/MembersList.js b/packages/react/src/components/Mentions/MembersList.js
index 3b9f7482c..b7ab4d3a4 100644
--- a/packages/react/src/components/Mentions/MembersList.js
+++ b/packages/react/src/components/Mentions/MembersList.js
@@ -1,46 +1,151 @@
-import React from 'react';
+import React, { useEffect } from 'react';
+import { css } from '@emotion/react';
import PropTypes from 'prop-types';
+import { Box } from '../Box';
+
+function MembersList({ mentionIndex, filteredMembers = [], onMemberClick }) {
+ const listStyle = css`
+ margin-bottom: 5px;
+ display: block;
+ max-height: 10rem;
+ overflow: scroll;
+ overflow-x: hidden;
+ max-height: 145px;
+ scrollbar-width: thin;
+ scrollbar-color: #e0e0e1 transparent;
+ &::-webkit-scrollbar {
+ width: 4px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: #e0e0e1;
+ border-radius: 4px;
+ }
+ &::-webkit-scrollbar-thumb:hover {
+ background-color: #e0e0e1;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+ `;
+
+ const listItemStyle = css`
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-left: 0;
+ padding-right: 2px;
+
+ &:hover {
+ background-color: #e8e8e8;
+ }
+ `;
+
+ const listTextStyle = css`
+ color: #000000;
+ font-weight: 600;
+ `;
+
+ const handleMemberClick = (selectedItem) => {
+ onMemberClick(selectedItem);
+ };
+
+ useEffect(() => {
+ const handleKeyPress = (event) => {
+ if (event.key === 'Enter') {
+ const selectedItem =
+ mentionIndex < filteredMembers.length
+ ? filteredMembers[mentionIndex]
+ : mentionIndex === filteredMembers.length
+ ? 'all'
+ : 'here';
+ handleMemberClick(selectedItem);
+ }
+ };
+
+ const handleKeyDown = (event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyPress);
+ document.addEventListener('keydown', handleKeyDown);
+
+ return () => {
+ document.removeEventListener('keydown', handleKeyPress);
+ document.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [mentionIndex, filteredMembers, handleMemberClick]);
-function MembersList({ mentionIndex, filteredMembers = [] }) {
return (
-
+
{filteredMembers.map((member, index) => (
- handleMemberClick(member)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleMemberClick(member);
+ }
+ }}
style={{
- backgroundColor: index === mentionIndex ? '#ddd' : 'white',
+ backgroundColor: index === mentionIndex && '#dddddd',
}}
>
- {member.name} @{member.username}
+
+ {member.name}
+
+ @{member.username}
+
))}
- handleMemberClick('all')}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleMemberClick('all');
+ }
+ }}
style={{
backgroundColor:
- mentionIndex === filteredMembers.length ? '#ddd' : 'white',
+ mentionIndex === filteredMembers.length && '#dddddd',
}}
>
- all
+ all
- handleMemberClick('here')}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleMemberClick('here');
+ }
+ }}
style={{
backgroundColor:
- mentionIndex === filteredMembers.length + 1 ? '#ddd' : 'white',
+ mentionIndex === filteredMembers.length + 1 && '#dddddd',
}}
>
- everyone
+ here
-
+
);
}
MembersList.propTypes = {
mentionIndex: PropTypes.any,
filteredMembers: PropTypes.array,
+ onMemberClick: PropTypes.func.isRequired,
};
export default MembersList;
diff --git a/packages/react/src/components/Menu/Menu.stories.js b/packages/react/src/components/Menu/Menu.stories.js
index 6a5235714..92da52d89 100644
--- a/packages/react/src/components/Menu/Menu.stories.js
+++ b/packages/react/src/components/Menu/Menu.stories.js
@@ -18,6 +18,11 @@ export const Menu = {
label: 'Threads',
icon: 'thread',
},
+ {
+ id: 'mentions',
+ label: 'Mentions',
+ icon: 'at',
+ },
{
id: 'members',
label: 'Members',
diff --git a/packages/react/src/components/MessageList/MessageList.js b/packages/react/src/components/MessageList/MessageList.js
index 9a9203a89..2cad03c4d 100644
--- a/packages/react/src/components/MessageList/MessageList.js
+++ b/packages/react/src/components/MessageList/MessageList.js
@@ -7,6 +7,8 @@ import {
useSearchMessageStore,
useChannelStore,
useUserStore,
+ useMentionsStore,
+ useThreadsMessageStore,
} from '../../store';
import RoomMembers from '../RoomMembers/RoomMember';
import MessageReportWindow from '../ReportMessage/MessageReportWindow';
@@ -14,10 +16,9 @@ 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);
@@ -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));
@@ -59,6 +61,7 @@ const MessageList = ({ messages }) => {
{showSearch && }
{showChannelinfo && }
{showAllThreads && }
+ {showMentions && }
>
);
};
diff --git a/packages/react/src/components/RoomMembers/inviteMembers/InviteMembers.js b/packages/react/src/components/RoomMembers/inviteMembers/InviteMembers.js
index 42af40076..c09de515a 100644
--- a/packages/react/src/components/RoomMembers/inviteMembers/InviteMembers.js
+++ b/packages/react/src/components/RoomMembers/inviteMembers/InviteMembers.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { css } from '@emotion/react';
import classes from '../RoomMember.module.css';
@@ -10,20 +10,26 @@ import { ActionButton } from '../../ActionButton';
const InviteMembers = ({ inviteData }) => {
const toggleInviteView = useInviteStore((state) => state.toggleInviteView);
+ const [isCopied, setIsCopied] = useState(false);
+ const copyToClipboard = (url) => {
+ navigator.clipboard.writeText(url);
+ setIsCopied(true);
+ };
return (
-
+
+ toggleInviteView()} ghost size="small">
+
+
+
{
>
Invite Members
- toggleInviteView()} ghost size="small">
-
-
{
width: 100%;
display: flex;
flex-direction: column;
+ margin-bottom: 5px;
`}
>
- Invite Link
-
+
+ Invite Link
+
+
+
+ copyToClipboard(inviteData.url)}
+ ghost
+ size="small"
+ css={css`
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ padding: 0;
+ `}
+ >
+
+
+
-
+
Your invite link will expire on{' '}
{new Date(inviteData.expires).toString().split('GMT')[0]}
diff --git a/packages/react/src/components/UserMentions/UserMentions.js b/packages/react/src/components/UserMentions/UserMentions.js
new file mode 100644
index 000000000..1bd655aa6
--- /dev/null
+++ b/packages/react/src/components/UserMentions/UserMentions.js
@@ -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 (
+
+
+
+
+
+
+
+ Mentions
+
+
+
+
+
+
+
+
+ {isLoaded && (
+
+ {mentionedMessages.length === 0 ? (
+
+
+
+ No mentions found
+
+
+ ) : (
+ mentionedMessages.map((message, index, arr) => {
+ const newDay =
+ index === 0 || isMessageNewDay(message, arr[index - 1]);
+ return (
+
+ {newDay ? (
+
+ {format(new Date(message.ts), 'MMMM d, yyyy')}
+
+ ) : null}
+
+ {showAvatar && (
+
+ )}
+
+
+
+
+ {message.attachments &&
+ message.attachments.length > 0 ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+
+ {!message.t && message.tcount && (
+
+ )}
+
+
+
+ );
+ })
+ )}
+
+ )}
+
+
+ );
+};
+
+export default UserMentions;
diff --git a/packages/react/src/components/UserMentions/UserMentions.module.css b/packages/react/src/components/UserMentions/UserMentions.module.css
new file mode 100644
index 000000000..cca051f27
--- /dev/null
+++ b/packages/react/src/components/UserMentions/UserMentions.module.css
@@ -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;
+ }
+}
diff --git a/packages/react/src/lib/searchToMentionUser.js b/packages/react/src/lib/searchToMentionUser.js
index 6dea02772..5ca80136c 100644
--- a/packages/react/src/lib/searchToMentionUser.js
+++ b/packages/react/src/lib/searchToMentionUser.js
@@ -7,7 +7,7 @@ export const searchToMentionUser = (
setmentionIndex,
setshowMembersList
) => {
- const lastChar = message[message.length - 1];
+ const lastChar = message ? message[message.length - 1] : '';
if (message.length === 0) {
setshowMembersList(false);
setStartReading(false);
@@ -29,22 +29,30 @@ export const searchToMentionUser = (
setmentionIndex(-1);
setshowMembersList(false);
} else {
- const c = message.lastIndexOf('@');
+ const query = message
+ .substring(message.lastIndexOf('@') + 1)
+ .toLowerCase();
+ const filteredMentionMembers = roomMembers.filter(
+ (member) =>
+ member.name.toLowerCase().includes(query) ||
+ member.username.toLowerCase().includes(query)
+ );
+
+ setFilteredMembers(filteredMentionMembers);
- setFilteredMembers(
- roomMembers.filter(
- (member) =>
- member.name
- .toLowerCase()
- .includes(message.substring(c + 1).toLowerCase()) ||
- member.username
- .toLowerCase()
- .includes(message.substring(c + 1).toLowerCase())
- )
+ const isValidUsername = roomMembers.some(
+ (member) =>
+ member.name.toLowerCase().includes(query) ||
+ member.username.toLowerCase().includes(query)
);
- setshowMembersList(true);
- setmentionIndex(0);
+ if (isValidUsername) {
+ setshowMembersList(true);
+ setmentionIndex(0);
+ } else {
+ setshowMembersList(false);
+ setmentionIndex(-1);
+ }
}
}
};
diff --git a/packages/react/src/store/index.js b/packages/react/src/store/index.js
index 1d734fbbe..78da56784 100644
--- a/packages/react/src/store/index.js
+++ b/packages/react/src/store/index.js
@@ -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';
diff --git a/packages/react/src/store/mentionmemberStore.js b/packages/react/src/store/mentionmemberStore.js
deleted file mode 100644
index 3025c9248..000000000
--- a/packages/react/src/store/mentionmemberStore.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { create } from 'zustand';
-
-const mentionmemberStore = create((set) => ({
- roomMembers: {},
- showMembersList: false,
- toggleShowMembers: (showMembersList) => set({ showMembersList }),
- setRoomMembers: (roomMembers) => set({ roomMembers }),
-}));
-
-export default mentionmemberStore;
diff --git a/packages/react/src/store/mentionsStore.js b/packages/react/src/store/mentionsStore.js
new file mode 100644
index 000000000..240f0602a
--- /dev/null
+++ b/packages/react/src/store/mentionsStore.js
@@ -0,0 +1,8 @@
+import { create } from 'zustand';
+
+const useMentionsStore = create((set) => ({
+ showMentions: false,
+ setShowMentions: (showMentions) => set(() => ({ showMentions })),
+}));
+
+export default useMentionsStore;