From 62c426858d0aa25dcf1b8d26581465f403b9d61b Mon Sep 17 00:00:00 2001 From: Anirban Singha Date: Fri, 27 Dec 2024 18:07:30 +0530 Subject: [PATCH 1/3] feat: add unstar, unpin, copy link, and navigate options in starred and pinned files list --- .../MessageAggregators/PinnedMessages.js | 28 +++++- .../MessageAggregators/StarredMessages.js | 25 ++++- .../common/MessageAggregator.js | 94 ++++++++++++++++--- .../common/MessageAggregator.styles.js | 2 + 4 files changed, 132 insertions(+), 17 deletions(-) diff --git a/packages/react/src/views/MessageAggregators/PinnedMessages.js b/packages/react/src/views/MessageAggregators/PinnedMessages.js index 884e13529..77ba07b83 100644 --- a/packages/react/src/views/MessageAggregators/PinnedMessages.js +++ b/packages/react/src/views/MessageAggregators/PinnedMessages.js @@ -1,14 +1,38 @@ -import React from 'react'; -import { useComponentOverrides } from '@embeddedchat/ui-elements'; +import React, { useContext } from 'react'; +import { + useComponentOverrides, + useToastBarDispatch, +} from '@embeddedchat/ui-elements'; import { MessageAggregator } from './common/MessageAggregator'; +import RCContext from '../../context/RCInstance'; const PinnedMessages = () => { const { variantOverrides } = useComponentOverrides('PinnedMessages'); const viewType = variantOverrides.viewType || 'Sidebar'; + const { RCInstance } = useContext(RCContext); + const dispatchToastMessage = useToastBarDispatch(); + + const unpin = async (msg) => { + const isPinned = msg.pinned; + const Unpin = await RCInstance.unpinMessage(msg._id); + if (Unpin.error) { + dispatchToastMessage({ + type: 'error', + message: 'Error pinning message', + }); + } else { + dispatchToastMessage({ + type: 'success', + message: isPinned ? 'Message unpinned' : 'Message pinned', + }); + } + }; return ( msg.pinned} viewType={viewType} diff --git a/packages/react/src/views/MessageAggregators/StarredMessages.js b/packages/react/src/views/MessageAggregators/StarredMessages.js index 5ced944f0..0be1e1b22 100644 --- a/packages/react/src/views/MessageAggregators/StarredMessages.js +++ b/packages/react/src/views/MessageAggregators/StarredMessages.js @@ -1,7 +1,11 @@ -import React, { useCallback, useEffect } from 'react'; -import { useComponentOverrides } from '@embeddedchat/ui-elements'; +import React, { useCallback, useContext } from 'react'; +import { + useComponentOverrides, + useToastBarDispatch, +} from '@embeddedchat/ui-elements'; import { useStarredMessageStore, useUserStore } from '../../store'; import { MessageAggregator } from './common/MessageAggregator'; +import RCContext from '../../context/RCInstance'; const StarredMessages = () => { const authenticatedUserId = useUserStore((state) => state.userId); @@ -10,16 +14,33 @@ const StarredMessages = () => { const starredMessages = useStarredMessageStore( (state) => state.starredMessages ); + const setStarredMessages = useStarredMessageStore( + (state) => state.setStarredMessages + ); + const dispatchToastMessage = useToastBarDispatch(); + const { RCInstance } = useContext(RCContext); const shouldRender = useCallback( (msg) => msg.starred && msg.starred.some((star) => star._id === authenticatedUserId), [authenticatedUserId] ); + + const unstar = async (msg) => { + await RCInstance.unstarMessage(msg._id); + dispatchToastMessage({ + type: 'success', + message: 'Message unstarred', + }); + setStarredMessages(starredMessages.filter((str) => str._id !== msg._id)); + }; + return ( state.messages); const threadMessages = useMessageStore((state) => state.threadMessages) || []; const allMessages = useMemo( @@ -44,6 +52,7 @@ export const MessageAggregator = ({ fetchedMessageList || searchFiltered || allMessages, shouldRender ); + const dispatchToastMessage = useToastBarDispatch(); const setShowSidebar = useSidebarStore((state) => state.setShowSidebar); const setJumpToMessage = (msgId) => { @@ -56,6 +65,28 @@ export const MessageAggregator = ({ } }; + const getMessageLink = async (id) => { + const host = await RCInstance.getHost(); + const res = await RCInstance.channelInfo(); + return `${host}/channel/${res.room.name}/?msg=${id}`; + }; + + const copyLink = async (id) => { + try { + const messageLink = await getMessageLink(id); + await navigator.clipboard.writeText(messageLink); + dispatchToastMessage({ + type: 'success', + message: 'Message link copied successfully', + }); + } catch (err) { + dispatchToastMessage({ + type: 'error', + message: 'Error in copying message link', + }); + } + }; + const isMessageNewDay = (current, previous) => !previous || shouldRender(previous) || @@ -136,18 +167,55 @@ export const MessageAggregator = ({ }} /> - setJumpToMessage(msg._id)} - css={{ - position: 'relative', - zIndex: 10, - marginRight: '5px', - }} - > - - + {!isStarredMessageDisplay && !isPinnedMessageDisplay && ( + setJumpToMessage(msg._id)} + css={{ + position: 'relative', + zIndex: 10, + marginRight: '5px', + }} + > + + + )} + {(isStarredMessageDisplay || isPinnedMessageDisplay) && ( + + unstar(msg) + : () => unpin(msg), + label: isStarredMessageDisplay + ? 'Remove star' + : 'Unpin', + icon: isStarredMessageDisplay ? 'star' : 'pin', + }, + { + id: 'copyLink', + action: () => copyLink(msg._id), + label: 'Copy link', + icon: 'link', + }, + { + id: 'navigate', + action: () => setJumpToMessage(msg._id), + label: 'Navigate', + icon: 'arrow-back', + }, + ]} + /> + + )} )} diff --git a/packages/react/src/views/MessageAggregators/common/MessageAggregator.styles.js b/packages/react/src/views/MessageAggregators/common/MessageAggregator.styles.js index 1f18222e3..625e3a057 100644 --- a/packages/react/src/views/MessageAggregators/common/MessageAggregator.styles.js +++ b/packages/react/src/views/MessageAggregators/common/MessageAggregator.styles.js @@ -9,6 +9,8 @@ const getMessageAggregatorStyles = () => { justify-content: initial; align-items: initial; max-width: 100%; + overflow-y: scroll; + padding-right: 0; `, noMessageStyles: css` From 09ae503d7a851bb43f7c3ac6a333b132b2400403 Mon Sep 17 00:00:00 2001 From: Anirban Singha Date: Thu, 2 Jan 2025 21:15:28 +0530 Subject: [PATCH 2/3] Add icon for jump-message and revised changes --- packages/react/src/hooks/useFetchChatData.js | 14 ++++++- packages/react/src/hooks/useRCAuth.js | 16 +------- packages/react/src/views/EmbeddedChat.js | 11 ----- .../common/MessageAggregator.js | 40 ++++++++++++------- .../src/components/Icon/icons/ArrowJump.js | 9 +++++ .../src/components/Icon/icons/index.js | 2 + 6 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 packages/ui-elements/src/components/Icon/icons/ArrowJump.js diff --git a/packages/react/src/hooks/useFetchChatData.js b/packages/react/src/hooks/useFetchChatData.js index 9eb04e9be..7a51eaceb 100644 --- a/packages/react/src/hooks/useFetchChatData.js +++ b/packages/react/src/hooks/useFetchChatData.js @@ -23,6 +23,12 @@ const useFetchChatData = (showRoles) => { const setViewUserInfoPermissions = useUserStore( (state) => state.setViewUserInfoPermissions ); + const setUserPinPermissions = useUserStore( + (state) => state.setUserPinPermissions + ); + const setEditMessagePermissions = useMessageStore( + (state) => state.setEditMessagePermissions + ); const getMessagesAndRoles = useCallback( async (anonymousMode) => { @@ -73,7 +79,13 @@ const useFetchChatData = (showRoles) => { } const permissions = await RCInstance.permissionInfo(); - setViewUserInfoPermissions(permissions.update[70]); + const permissionsMap = permissions.update.reduce((map, item) => { + map[item._id] = item; + return map; + }); + setUserPinPermissions(permissionsMap['pin-message']); + setEditMessagePermissions(permissionsMap['edit-message']); + setViewUserInfoPermissions(permissionsMap['view-full-other-user-info']); } catch (e) { console.error(e); } diff --git a/packages/react/src/hooks/useRCAuth.js b/packages/react/src/hooks/useRCAuth.js index c0b1d3a3b..83b013353 100644 --- a/packages/react/src/hooks/useRCAuth.js +++ b/packages/react/src/hooks/useRCAuth.js @@ -1,12 +1,7 @@ import { useContext } from 'react'; import { useToastBarDispatch } from '@embeddedchat/ui-elements'; import RCContext from '../context/RCInstance'; -import { - useUserStore, - totpModalStore, - useLoginStore, - useMessageStore, -} from '../store'; +import { useUserStore, totpModalStore, useLoginStore } from '../store'; export const useRCAuth = () => { const { RCInstance } = useContext(RCContext); @@ -25,18 +20,11 @@ export const useRCAuth = () => { ); const setPassword = useUserStore((state) => state.setPassword); const setEmailorUser = useUserStore((state) => state.setEmailorUser); - const setUserPinPermissions = useUserStore( - (state) => state.setUserPinPermissions - ); - const setEditMessagePermissions = useMessageStore( - (state) => state.setEditMessagePermissions - ); const dispatchToastMessage = useToastBarDispatch(); const handleLogin = async (userOrEmail, password, code) => { try { const res = await RCInstance.login(userOrEmail, password, code); - const permissions = await RCInstance.permissionInfo(); if (res.error === 'Unauthorized' || res.error === 403) { dispatchToastMessage({ type: 'error', @@ -68,8 +56,6 @@ export const useRCAuth = () => { setIsTotpModalOpen(false); setEmailorUser(null); setPassword(null); - setUserPinPermissions(permissions.update[150]); - setEditMessagePermissions(permissions.update[28]); dispatchToastMessage({ type: 'success', message: 'Successfully logged in', diff --git a/packages/react/src/views/EmbeddedChat.js b/packages/react/src/views/EmbeddedChat.js index 17b1ff1a4..f3b94c7b4 100644 --- a/packages/react/src/views/EmbeddedChat.js +++ b/packages/react/src/views/EmbeddedChat.js @@ -66,7 +66,6 @@ const EmbeddedChat = (props) => { const [isSynced, setIsSynced] = useState(!remoteOpt); const { getToken, saveToken, deleteToken } = getTokenStorage(secure); const { - isUserAuthenticated, setIsUserAuthenticated, setUsername: setAuthenticatedUsername, setUserAvatarUrl: setAuthenticatedAvatarUrl, @@ -84,13 +83,6 @@ const EmbeddedChat = (props) => { })); const setIsLoginIn = useLoginStore((state) => state.setIsLoginIn); - const setUserPinPermissions = useUserStore( - (state) => state.setUserPinPermissions - ); - - const setEditMessagePermissions = useMessageStore( - (state) => state.setEditMessagePermissions - ); if (isClosable && !setClosableState) { throw Error( 'Please provide a setClosableState to props when isClosable = true' @@ -132,9 +124,6 @@ const EmbeddedChat = (props) => { setIsLoginIn(true); try { await RCInstance.autoLogin(auth); - const permissions = await RCInstance.permissionInfo(); - setUserPinPermissions(permissions.update[150]); - setEditMessagePermissions(permissions.update[28]); } catch (error) { console.error(error); } finally { diff --git a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js index 7ed861a44..b5414147b 100644 --- a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js +++ b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js @@ -14,7 +14,7 @@ import RCContext from '../../../context/RCInstance'; import { MessageDivider } from '../../Message/MessageDivider'; import Message from '../../Message/Message'; import getMessageAggregatorStyles from './MessageAggregator.styles'; -import { useMessageStore, useSidebarStore } from '../../../store'; +import { useMessageStore, useSidebarStore, useUserStore } from '../../../store'; import { useSetMessageList } from '../../../hooks/useSetMessageList'; import LoadingIndicator from './LoadingIndicator'; import NoMessagesIndicator from './NoMessageIndicator'; @@ -42,6 +42,7 @@ export const MessageAggregator = ({ const setExclusiveState = useSetExclusiveState(); const { RCInstance } = useContext(RCContext); const messages = useMessageStore((state) => state.messages); + const currentUserRoles = useUserStore((state) => state.roles); const threadMessages = useMessageStore((state) => state.threadMessages) || []; const allMessages = useMemo( () => [...messages, ...threadMessages], @@ -53,6 +54,12 @@ export const MessageAggregator = ({ shouldRender ); const dispatchToastMessage = useToastBarDispatch(); + const pinPermissions = useUserStore( + (state) => state.userPinPermissions.roles + ); + + const pinRoles = new Set(pinPermissions); + const isAllowedToPin = currentUserRoles.some((role) => pinRoles.has(role)); const setShowSidebar = useSidebarStore((state) => state.setShowSidebar); const setJumpToMessage = (msgId) => { @@ -190,16 +197,21 @@ export const MessageAggregator = ({ unstar(msg) - : () => unpin(msg), - label: isStarredMessageDisplay - ? 'Remove star' - : 'Unpin', - icon: isStarredMessageDisplay ? 'star' : 'pin', - }, + isPinnedMessageDisplay && isAllowedToPin + ? { + id: 'unpin', + action: () => unpin(msg), + label: 'Unpin', + icon: 'pin', + } + : isStarredMessageDisplay + ? { + id: 'unstar', + action: () => unstar(msg), + label: 'Remove star', + icon: 'star', + } + : {}, { id: 'copyLink', action: () => copyLink(msg._id), @@ -207,10 +219,10 @@ export const MessageAggregator = ({ icon: 'link', }, { - id: 'navigate', + id: 'jumptomessage', action: () => setJumpToMessage(msg._id), - label: 'Navigate', - icon: 'arrow-back', + label: 'Jump to message', + icon: 'arrow-jump', }, ]} /> diff --git a/packages/ui-elements/src/components/Icon/icons/ArrowJump.js b/packages/ui-elements/src/components/Icon/icons/ArrowJump.js new file mode 100644 index 000000000..17e69514b --- /dev/null +++ b/packages/ui-elements/src/components/Icon/icons/ArrowJump.js @@ -0,0 +1,9 @@ +import React from 'react'; + +const ArrowJump = (props) => ( + + + +); + +export default ArrowJump; diff --git a/packages/ui-elements/src/components/Icon/icons/index.js b/packages/ui-elements/src/components/Icon/icons/index.js index 77cd6a451..f348ba067 100644 --- a/packages/ui-elements/src/components/Icon/icons/index.js +++ b/packages/ui-elements/src/components/Icon/icons/index.js @@ -61,6 +61,7 @@ import Arc from './Arc'; import Avatar from './Avatar'; import FormatText from './FormatText'; import Cog from './Cog'; +import ArrowJump from './ArrowJump'; const icons = { file: File, @@ -126,6 +127,7 @@ const icons = { avatar: Avatar, 'format-text': FormatText, cog: Cog, + 'arrow-jump': ArrowJump, }; export default icons; From 521b5df8687aa7219fef74eee1ab459050f37e2b Mon Sep 17 00:00:00 2001 From: Anirban Singha Date: Sun, 5 Jan 2025 20:26:03 +0530 Subject: [PATCH 3/3] Resolve lint error --- .../src/views/MessageAggregators/common/MessageAggregator.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js index 25c23f81d..5438861c5 100644 --- a/packages/react/src/views/MessageAggregators/common/MessageAggregator.js +++ b/packages/react/src/views/MessageAggregators/common/MessageAggregator.js @@ -20,7 +20,6 @@ import LoadingIndicator from './LoadingIndicator'; import NoMessagesIndicator from './NoMessageIndicator'; import FileDisplay from '../../FileMessage/FileMessage'; import useSetExclusiveState from '../../../hooks/useSetExclusiveState'; -import { useRCContext } from '../../../context/RCInstance'; export const MessageAggregator = ({ title, @@ -41,8 +40,7 @@ export const MessageAggregator = ({ const { theme } = useTheme(); const styles = getMessageAggregatorStyles(theme); const setExclusiveState = useSetExclusiveState(); - const { RCInstance } = useContext(RCContext); - const { ECOptions } = useRCContext(); + const { RCInstance, ECOptions } = useContext(RCContext); const showRoles = ECOptions?.showRoles; const messages = useMessageStore((state) => state.messages); const currentUserRoles = useUserStore((state) => state.roles);