Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reactions on Messages(sqservices -1734) #14771

Merged
merged 44 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c104199
feat: create floating menu for message actions(SQSERVICES-1910)
arjita-mitra Feb 17, 2023
f3890e0
feat: manage message actions menu focus, hover, active states(SQSERVI…
arjita-mitra Feb 27, 2023
4e35a46
test: message action menu tests
arjita-mitra Feb 27, 2023
bbf1861
feat: emoji picker integration(SQSERVICES-1734)
arjita-mitra Mar 1, 2023
989a0fd
better positioning of emoji picker
thisisamir98 Mar 1, 2023
516513e
feat: handle outside area clicked for reaction/action menu items
arjita-mitra Mar 2, 2023
8886ae3
feat: configure feature flag to enable/disable reaction
arjita-mitra Mar 2, 2023
39aa18e
Merge branch 'dev' of https://github.com/wireapp/wire-webapp into fea…
arjita-mitra Mar 2, 2023
92d8f1d
Merge branch 'dev' of https://github.com/wireapp/wire-webapp into fea…
arjita-mitra Mar 20, 2023
8a70fd6
feat: add ability to react on messages(SQSERVICES-1734) (#14839)
arjita-mitra Mar 21, 2023
45fc621
feat: Add tooltip to reaction buttons (#14933)
thisisamir98 Mar 30, 2023
f0910b6
feat: add new design for message timestamp, read receipt and integrat…
arjita-mitra Apr 13, 2023
6c32cee
runfix: Reaction views menu on 1 to 1
thisisamir98 Apr 14, 2023
d74a759
runfix: Adjust margin of reaction tooltip
thisisamir98 Apr 14, 2023
2c2f22b
chore: remove img cdn
arjita-mitra Apr 14, 2023
fad0c02
Merge branch 'dev' of https://github.com/wireapp/wire-webapp into fea…
arjita-mitra May 9, 2023
bdf0312
chore: merge commit with dev
arjita-mitra May 9, 2023
713f676
Merge branch 'feature/SQSERVICES-1864' of https://github.com/wireapp/…
arjita-mitra May 9, 2023
7f280fd
fix: test errors
arjita-mitra May 9, 2023
b07a7ac
fix: lock file update
arjita-mitra May 9, 2023
0ff50d8
fix: add skiplibcheck temporarily
arjita-mitra May 10, 2023
ece7e94
test: add withTheme
thisisamir98 May 10, 2023
7ba346e
fix: test cases
arjita-mitra May 10, 2023
041fafb
feat: message reactions design review feedback changes(SQSERVICES-205…
arjita-mitra May 22, 2023
b82af13
fix: improve focus and hover behaviour of reactions menu (#15227)
arjita-mitra May 23, 2023
ad0ba64
fix: use emoji image inside reaction details right panel, some style …
arjita-mitra May 24, 2023
a616a33
fix: reaction QA feedback(WPB-1199) (#15321)
arjita-mitra Jun 16, 2023
b3fb9d6
fix: reset active menu button when no menu is open and clicked on wra…
arjita-mitra Jun 16, 2023
40f7e28
chore: merge dev
tlebon Jun 20, 2023
d5ceb9f
chore: cleanup packages
tlebon Jun 20, 2023
88a0a0f
fix: remove reply message option from context menu (#15478)
arjita-mitra Jul 19, 2023
0668169
fix: Capitalise the first letter of the first word in the emoji name …
arjita-mitra Jul 19, 2023
329e4cb
feat: emoji pills should become active/inactive when user selects/uns…
arjita-mitra Jul 26, 2023
3c29447
fix: make all emoji pills active that user ever reacted to[WPB-3291] …
arjita-mitra Jul 27, 2023
2d4bc97
feat: emoji pills should be sorted by number of reactions in desc ord…
arjita-mitra Jul 31, 2023
490b96a
feat: retain user selected skin colour after user reacts with the sel…
arjita-mitra Jul 31, 2023
ebbe845
Merge branch 'dev' of https://github.com/wireapp/wire-webapp into fea…
arjita-mitra Aug 18, 2023
b4d80b7
test: fix test
arjita-mitra Aug 18, 2023
5ef87cf
fix: yarn lock
arjita-mitra Aug 18, 2023
896be75
chore: fix lock file
arjita-mitra Aug 18, 2023
76b12f5
fix: test case
arjita-mitra Aug 18, 2023
fd49b94
fix linting
atomrc Aug 21, 2023
d3fda72
fix: remove unecessary file
atomrc Aug 21, 2023
9d880a4
Merge branch 'dev' into feature/SQSERVICES-1864
atomrc Aug 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"countly-sdk-web": "23.6.0",
"date-fns": "2.30.0",
"dexie-batch": "0.4.3",
"emoji-picker-react": "^4.4.7",
"highlight.js": "11.8.0",
"http-status-codes": "2.2.0",
"jimp": "0.22.10",
Expand Down Expand Up @@ -80,7 +81,7 @@
"@types/node": "^20.5.0",
"@types/open-graph": "0.2.2",
"@types/platform": "1.3.4",
"@types/react": "18.2.18",
"@types/react": "18.2.20",
"@types/react-dom": "18.2.7",
"@types/react-redux": "7.1.25",
"@types/react-transition-group": "4.4.6",
Expand Down
13 changes: 10 additions & 3 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"accessibility.conversation.goBack": "Go back to conversation info",
"accessibility.conversation.sectionLabel": "Conversation List",
"accessibility.conversationAssetImageAlt": "Image from {{username}} from {{messageDate}}",
"accessibility.conversationContextMenuOpenLabel": "Open messaging options",
"accessibility.conversationContextMenuOpenLabel": "Open more options",
"accessibility.conversationDetailsActionDevicesLabel": "Show Devices",
"accessibility.conversationDetailsActionGroupAdminLabel": "Set group admin",
"accessibility.conversationDetailsActionNotificationsLabel": "Show notifications settings",
Expand Down Expand Up @@ -305,6 +305,11 @@
"conversationContextMenuLike": "Like",
"conversationContextMenuReply": "Reply",
"conversationContextMenuUnlike": "Unlike",
"accessibility.messageActionsMenuThumbsUp": "React with thumbs up",
"accessibility.messageActionsMenuLike": "React with heart",
"accessibility.messageActionsMenuEmoji": "Select emoji",
"accessibility.messageActionsMenuLabel": "Message actions",
"accessibility.emojiPickerSearchPlaceholder": "Search for emoji",
"conversationCreateReceiptsEnabled": "Read receipts are on",
"conversationCreateTeam": "with [showmore]all team members[/showmore]",
"conversationCreateTeamGuest": "with [showmore]all team members and one guest[/showmore]",
Expand Down Expand Up @@ -389,6 +394,7 @@
"conversationLabelGroups": "Groups",
"conversationLabelPeople": "People",
"conversationLikesCaption": "{{number}} people",
"conversationLikesCaptionReacted": "reacted with {{emojiName}}",
"conversationLocationLink": "Open Map",
"conversationMemberJoined": "[bold]{{name}}[/bold] added {{users}} to the conversation",
"conversationMemberJoinedMore": "[bold]{{name}}[/bold] added {{users}}, and [showmore]{{count}} more[/showmore] to the conversation",
Expand Down Expand Up @@ -695,12 +701,13 @@
"messageCouldNotBeSentConnectivityIssues": "Message could not be sent due to connectivity issues.",
"messageCouldNotBeSentRetry": "Retry",
"messageDetailsEdited": "Edited: {{edited}}",
"messageDetailsNoLikes": "No one has liked this message yet.",
"messageDetailsNoReactions": "No one has reacted to this message yet.",
"messageDetailsNoReceipts": "No one has read this message yet.",
"messageDetailsReceiptsOff": "Read receipts were not on when this message was sent.",
"messageDetailsSent": "Sent: {{sent}}",
"messageDetailsTitle": "Details",
"messageDetailsTitleLikes": "Liked{{count}}",
"messageDetailsTitleReactions": "Reactions{{count}}",
"messageReactionDetails": "{{emojiCount}} reaction, react with {{emojiName}} emoji",
"messageDetailsTitleReceipts": "Read{{count}}",
"messageFailedToSendHideDetails": "Hide details",
"messageFailedToSendParticipants": "{{count}} participants",
Expand Down
10 changes: 8 additions & 2 deletions src/script/components/Conversation/Conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,16 @@ export const Conversation: FC<ConversationProps> = ({
}
};

const showMessageDetails = (message: Message, showLikes = false) => {
const showMessageDetails = (message: Message, showReactions = false) => {
if (!is1to1) {
openRightSidebar(PanelState.MESSAGE_DETAILS, {entity: message, showLikes}, true);
openRightSidebar(PanelState.MESSAGE_DETAILS, {entity: message, showReactions}, true);
}
};

const showMessageReactions = (message: Message, showReactions = true) => {
openRightSidebar(PanelState.MESSAGE_DETAILS, {entity: message, showReactions});
};

const handleEmailClick = (event: Event, messageDetails: MessageDetails) => {
safeMailOpen(messageDetails.href!);
event.preventDefault();
Expand Down Expand Up @@ -343,6 +347,7 @@ export const Conversation: FC<ConversationProps> = ({
conversationRepository: repositories.conversation,
currentMessageEntity: messageEntity,
messageRepository: repositories.message,
selfUser,
});
};

Expand Down Expand Up @@ -503,6 +508,7 @@ export const Conversation: FC<ConversationProps> = ({
cancelConnectionRequest={clickOnCancelRequest}
showUserDetails={showUserDetails}
showMessageDetails={showMessageDetails}
showMessageReactions={showMessageReactions}
showParticipants={showParticipants}
showImageDetails={showDetail}
resetSession={onSessionResetClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ describe('message', () => {
onClickCancelRequest: jest.fn(),
onClickImage: jest.fn(),
onClickInvitePeople: jest.fn(),
onClickLikes: jest.fn(),
onClickMessage: jest.fn(),
onClickParticipants: jest.fn(),
onClickReceipts: jest.fn(),
onClickReaction: jest.fn(),
onClickReactionDetails: jest.fn(),
onClickDetails: jest.fn(),
onClickTimestamp: jest.fn(),
onLike: jest.fn(),
onRetry: jest.fn(),
previousMessage: undefined,
selfId: {domain: '', id: createUuid()},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,31 @@
*
*/

import React, {useMemo} from 'react';
import React, {useMemo, useState, useEffect} from 'react';

import {QualifiedId} from '@wireapp/api-client/lib/user';

import {Conversation} from 'src/script/entity/Conversation';
import {CompositeMessage} from 'src/script/entity/message/CompositeMessage';
import {ContentMessage} from 'src/script/entity/message/ContentMessage';
import {Message} from 'src/script/entity/message/Message';
import {useRelativeTimestamp} from 'src/script/hooks/useRelativeTimestamp';
import {StatusType} from 'src/script/message/StatusType';
import {useKoSubscribableChildren} from 'Util/ComponentUtil';
import {getMessageAriaLabel} from 'Util/conversationMessages';
import {KEY} from 'Util/KeyboardUtil';
import {t} from 'Util/LocalizerUtil';
import {setContextMenuPosition} from 'Util/util';
import {groupByReactionUsers} from 'Util/ReactionUtil';

import {ContentAsset} from './asset';
import {MessageFooterLike} from './MessageFooterLike';
import {MessageActionsMenu} from './MessageActions/MessageActions';
import {useMessageActionsState} from './MessageActions/MessageActions.state';
import {MessageReactionsList} from './MessageActions/MessageReactions/MessageReactionsList';
import {MessageHeader} from './MessageHeader';
import {MessageLike} from './MessageLike';
import {Quote} from './MessageQuote';
import {CompleteFailureToSendWarning, PartialFailureToSendWarning} from './Warnings';

import {MessageActions} from '..';
import {EphemeralStatusType} from '../../../../message/EphemeralStatusType';
import {ContextMenuEntry, showContextMenu} from '../../../../ui/ContextMenu';
import {ContextMenuEntry} from '../../../../ui/ContextMenu';
import {EphemeralTimer} from '../EphemeralTimer';
import {MessageTime} from '../MessageTime';
import {ReadReceiptStatus} from '../ReadReceiptStatus';
Expand All @@ -62,44 +62,44 @@ export interface ContentMessageProps extends Omit<MessageActions, 'onClickResetS
quotedMessage?: ContentMessage;
selfId: QualifiedId;
isMsgElementsFocusable: boolean;
onClickReaction: (emoji: string) => void;
}

const ContentMessageComponent: React.FC<ContentMessageProps> = ({
conversation,
message,
findMessage,
selfId,
hasMarker,
hasMarker = false,
isMessageFocused,
isLastDeliveredMessage,
contextMenu,
previousMessage,
onClickReceipts,
onClickAvatar,
onClickImage,
onClickTimestamp,
onClickMessage,
onClickLikes,
onClickReactionDetails,
onClickButton,
onLike,
onRetry,
isMsgElementsFocusable,
onClickReaction,
}) => {
// check if current message is focused and its elements focusable
const msgFocusState = useMemo(
() => isMsgElementsFocusable && isMessageFocused,
[isMsgElementsFocusable, isMessageFocused],
);
const messageFocusedTabIndex = useMessageFocusedTabIndex(msgFocusState);
const {entries: menuEntries} = useKoSubscribableChildren(contextMenu, ['entries']);
const {
senderName,
timestamp,
ephemeral_caption,
ephemeral_status,
assets,
other_likes,
was_edited,
failedToSend,
reactions,
status,
user,
} = useKoSubscribableChildren(message, [
Expand All @@ -111,6 +111,7 @@ const ContentMessageComponent: React.FC<ContentMessageProps> = ({
'other_likes',
'was_edited',
'failedToSend',
'reactions',
'status',
'user',
]);
Expand All @@ -126,27 +127,53 @@ const ContentMessageComponent: React.FC<ContentMessageProps> = ({

return !previousMessage.isContent() || previousMessage.user().id !== user.id;
};

const handleContextKeyDown = (event: React.KeyboardEvent) => {
if ([KEY.SPACE, KEY.ENTER].includes(event.key)) {
const newEvent = setContextMenuPosition(event);
showContextMenu(newEvent, menuEntries, 'message-options-menu');
}
};

const timeAgo = useRelativeTimestamp(message.timestamp());
const [messageAriaLabel] = getMessageAriaLabel({
assets,
displayTimestampShort: message.displayTimestampShort(),
senderName,
});

const [isActionMenuVisible, setActionMenuVisibility] = useState(true);
const isMenuOpen = useMessageActionsState(state => state.isMenuOpen);
useEffect(() => {
if (isMessageFocused || msgFocusState) {
setActionMenuVisibility(true);
} else {
setActionMenuVisibility(false);
}
}, [msgFocusState, isMessageFocused]);

const reactionGroupedByUser = groupByReactionUsers(reactions);
const reactionsTotalCount = Array.from(reactionGroupedByUser).length;

return (
<div aria-label={messageAriaLabel}>
<div
aria-label={messageAriaLabel}
className="content-message-wrapper"
onMouseEnter={event => {
// open another floating action menu if none already open
if (!isMenuOpen) {
setActionMenuVisibility(true);
}
}}
onMouseLeave={event => {
// close floating message actions when no active menu is open like context menu/emoji picker
if (!isMenuOpen) {
setActionMenuVisibility(false);
}
}}
>
{shouldShowAvatar() && (
<MessageHeader onClickAvatar={onClickAvatar} message={message} focusTabIndex={messageFocusedTabIndex}>
{was_edited && (
<span className="message-header-label-icon icon-edit" title={message.displayEditedTimestamp()}></span>
)}
<span className="content-message-timestamp">
<MessageTime timestamp={timestamp} className="label-xs" data-timestamp-type="normal">
{timeAgo}
</MessageTime>
</span>
</MessageHeader>
)}
{message.quote() && (
Expand All @@ -168,7 +195,6 @@ const ContentMessageComponent: React.FC<ContentMessageProps> = ({
<EphemeralTimer message={message} />
</div>
)}

{assets.map(asset => (
<ContentAsset
key={asset.type}
Expand All @@ -181,7 +207,6 @@ const ContentMessageComponent: React.FC<ContentMessageProps> = ({
isMessageFocused={msgFocusState}
/>
))}

{failedToSend && (
<PartialFailureToSendWarning
isMessageFocused={msgFocusState}
Expand All @@ -197,68 +222,37 @@ const ContentMessageComponent: React.FC<ContentMessageProps> = ({
onRetry={() => onRetry(message)}
/>
)}

{!other_likes.length && message.isReactable() && (
<div className="message-body-like">
<MessageLike
className="message-body-like-icon like-button message-show-on-hover"
message={message}
onLike={onLike}
isMessageFocused={msgFocusState}
/>
</div>
)}

<div className="message-body-actions">
{menuEntries.length > 0 && (
<button
tabIndex={messageFocusedTabIndex}
className="context-menu icon-more font-size-xs"
aria-label={t('accessibility.conversationContextMenuOpenLabel')}
onKeyDown={handleContextKeyDown}
onClick={event => showContextMenu(event, menuEntries, 'message-options-menu')}
></button>
)}

{ephemeral_status === EphemeralStatusType.ACTIVE && (
<time
className="time"
data-uie-uid={message.id}
title={ephemeral_caption}
data-timestamp={timestamp}
data-bind="showAllTimestamps"
>
{message.displayTimestampShort()}
</time>
)}

{ephemeral_status !== EphemeralStatusType.ACTIVE && (
<MessageTime data-uie-uid={message.id} timestamp={timestamp}>
{message.displayTimestampShort()}
</MessageTime>
)}

<ReadReceiptStatus
message={message}
is1to1Conversation={conversation.is1to1()}
isLastDeliveredMessage={isLastDeliveredMessage}
onClickReceipts={onClickReceipts}
isMessageFocused={msgFocusState}
/>
</div>
</div>

{other_likes.length > 0 && (
<div>
<MessageFooterLike
{isActionMenuVisible && (
<MessageActionsMenu
isMsgWithHeader={shouldShowAvatar()}
message={message}
is1to1Conversation={conversation.is1to1()}
onLike={onLike}
onClickLikes={onClickLikes}
handleActionMenuVisibility={setActionMenuVisibility}
contextMenu={contextMenu}
isMessageFocused={msgFocusState}
messageWithSection={hasMarker}
handleReactionClick={onClickReaction}
reactionsTotalCount={reactionsTotalCount}
isRemovedFromConversation={conversation.removed_from_conversation()}
/>
</div>
)}
)}
</div>

<MessageReactionsList
reactions={reactions}
userId={selfId.id}
handleReactionClick={onClickReaction}
isMessageFocused={msgFocusState}
onTooltipReactionCountClick={() => onClickReactionDetails(message)}
onLastReactionKeyEvent={() => setActionMenuVisibility(false)}
isRemovedFromConversation={conversation.removed_from_conversation()}
/>
</div>
);
};
Expand Down
Loading