Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions apps/meteor/client/components/message/MessageContentBody.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { MessageBody, Skeleton } from '@rocket.chat/fuselage';
import { Markup } from '@rocket.chat/gazzodown';
import type { ComponentProps } from 'react';
import React, { Suspense } from 'react';

import type { MessageWithMdEnforced } from '../../lib/parseMessageTextToAstMarkdown';
import GazzodownText from '../GazzodownText';

type MessageContentBodyProps = Pick<MessageWithMdEnforced, 'mentions' | 'channels' | 'md'> & {
searchText?: string;
};
} & ComponentProps<typeof MessageBody>;

const MessageContentBody = ({ mentions, channels, md, searchText }: MessageContentBodyProps) => (
<MessageBody data-qa-type='message-body' dir='auto'>
const MessageContentBody = ({ mentions, channels, md, searchText, ...props }: MessageContentBodyProps) => (
<MessageBody data-qa-type='message-body' dir='auto' {...props}>
<Suspense fallback={<Skeleton />}>
<GazzodownText channels={channels} mentions={mentions} searchText={searchText}>
<Markup tokens={md} />
Expand Down
5 changes: 4 additions & 1 deletion apps/meteor/client/components/message/MessageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const MessageHeader = ({ message }: MessageHeaderProps): ReactElement => {
<MessageNameContainer
tabIndex={0}
role='button'
id={`${message._id}-displayName`}
aria-label={getUserDisplayName(user.name, user.username, showRealName)}
onClick={(e) => openUserCard(e, message.u.username)}
onKeyDown={(e: KeyboardEvent<HTMLSpanElement>) => {
Expand All @@ -72,7 +73,9 @@ const MessageHeader = ({ message }: MessageHeaderProps): ReactElement => {
)}
</MessageNameContainer>
{shouldShowRolesList && <MessageRoles roles={roles} isBot={message.bot} />}
<MessageTimestamp title={formatDateAndTime(message.ts)}>{formatTime(message.ts)}</MessageTimestamp>
<MessageTimestamp id={`${message._id}-time`} title={formatDateAndTime(message.ts)}>
{formatTime(message.ts)}
</MessageTimestamp>
{message.private && <MessageStatusPrivateIndicator>{t('Only_you_can_see_this_message')}</MessageStatusPrivateIndicator>}
<StatusIndicators message={message} />
</FuselageMessageHeader>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { MessageBlock, MessageMetrics, MessageMetricsItem, MessageMetricsReply } from '@rocket.chat/fuselage';
import {
MessageBlock,
MessageMetrics,
MessageMetricsItem,
MessageMetricsItemIcon,
MessageMetricsItemLabel,
MessageMetricsReply,
} from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';
Expand Down Expand Up @@ -26,8 +33,8 @@ const DiscussionMetrics = ({ lm, count, rid, drid }: DiscussionMetricsProps): Re
{count ? t('message_counter', { count }) : t('Reply')}
</MessageMetricsReply>
<MessageMetricsItem title={lm?.toLocaleString()}>
<MessageMetricsItem.Icon name='clock' />
<MessageMetricsItem.Label>{lm ? format(lm) : t('No_messages_yet')}</MessageMetricsItem.Label>
<MessageMetricsItemIcon name='clock' />
<MessageMetricsItemLabel>{lm ? format(lm) : t('No_messages_yet')}</MessageMetricsItemLabel>
</MessageMetricsItem>
</MessageMetrics>
</MessageBlock>
Expand Down
49 changes: 32 additions & 17 deletions apps/meteor/client/components/message/content/Reactions.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
import { useToolbar } from '@react-aria/toolbar';
import type { IMessage } from '@rocket.chat/core-typings';
import { MessageReactions, MessageReactionAction } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import React, { useContext } from 'react';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes, KeyboardEvent, ReactElement } from 'react';
import React, { useContext, useRef } from 'react';

import { MessageListContext, useOpenEmojiPicker, useUserHasReacted } from '../list/MessageListContext';
import Reaction from './reactions/Reaction';
import { useToggleReactionMutation } from './reactions/useToggleReactionMutation';

type ReactionsProps = {
message: IMessage;
};
} & HTMLAttributes<HTMLDivElement>;

const Reactions = ({ message }: ReactionsProps): ReactElement => {
const Reactions = ({ message, ...props }: ReactionsProps): ReactElement => {
const t = useTranslation();
const ref = useRef(null);
const hasReacted = useUserHasReacted(message);
const openEmojiPicker = useOpenEmojiPicker(message);
const { username } = useContext(MessageListContext);
const toggleReactionMutation = useToggleReactionMutation();
const { toolbarProps } = useToolbar(props, ref);

return (
<MessageReactions>
{message.reactions &&
Object.entries(message.reactions).map(([name, reactions]) => (
<Reaction
key={name}
counter={reactions.usernames.length}
hasReacted={hasReacted}
name={name}
names={reactions.usernames.filter((user) => user !== username).map((username) => `@${username}`)}
messageId={message._id}
onClick={() => toggleReactionMutation.mutate({ mid: message._id, reaction: name })}
/>
))}
<MessageReactionAction onClick={openEmojiPicker} />
{message.reactions && (
<div ref={ref} {...toolbarProps} {...props}>
{Object.entries(message.reactions).map(([name, reactions]) => (
<Reaction
key={name}
counter={reactions.usernames.length}
hasReacted={hasReacted}
name={name}
names={reactions.usernames.filter((user) => user !== username).map((username) => `@${username}`)}
messageId={message._id}
onKeyDown={(e: KeyboardEvent) =>
(e.code === 'Space' || e.code === 'Enter') && toggleReactionMutation.mutate({ mid: message._id, reaction: name })
}
onClick={() => toggleReactionMutation.mutate({ mid: message._id, reaction: name })}
/>
))}
</div>
)}
<MessageReactionAction
title={t('Add_Reaction')}
onKeyDown={(e: KeyboardEvent) => (e.code === 'Space' || e.code === 'Enter') && openEmojiPicker(e)}
onClick={openEmojiPicker}
/>
</MessageReactions>
);
};
Expand Down
13 changes: 6 additions & 7 deletions apps/meteor/client/components/message/content/ThreadMetrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,12 @@ const ThreadMetrics = ({ unread, mention, all, rid, mid, counter, participants,
<MessageMetricsItem.Icon name='clock' />
<MessageMetricsItem.Label>{format(lm)}</MessageMetricsItem.Label>
</MessageMetricsItem>
<MessageMetricsItem
className={!following ? anchor : undefined}
title={t(following ? 'Following' : 'Not_following')}
data-rid={rid}
onClick={handleFollow}
>
<MessageMetricsFollowing name={following ? 'bell' : 'bell-off'} />
<MessageMetricsItem className={!following ? anchor : undefined} data-rid={rid}>
<MessageMetricsFollowing
title={t(following ? 'Following' : 'Not_following')}
name={following ? 'bell' : 'bell-off'}
onClick={handleFollow}
/>
</MessageMetricsItem>
{(mention || all || unread) && (
<MessageMetricsItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MessageReaction as MessageReactionTemplate, MessageReactionEmoji, MessageReactionCounter } from '@rocket.chat/fuselage';
import { useTooltipClose, useTooltipOpen } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import { useTooltipClose, useTooltipOpen, useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps, ReactElement } from 'react';
import React, { useRef, useContext } from 'react';

import { getEmojiClassNameAndDataTitle } from '../../../../lib/utils/renderEmoji';
Expand All @@ -14,10 +14,10 @@ type ReactionProps = {
name: string;
names: string[];
messageId: string;
onClick: () => void;
};
} & ComponentProps<typeof MessageReactionTemplate>;

const Reaction = ({ hasReacted, counter, name, names, messageId, ...props }: ReactionProps): ReactElement => {
const t = useTranslation();
const ref = useRef<HTMLDivElement>(null);
const openTooltip = useTooltipOpen();
const closeTooltip = useTooltipClose();
Expand All @@ -32,8 +32,7 @@ const Reaction = ({ hasReacted, counter, name, names, messageId, ...props }: Rea
ref={ref}
key={name}
mine={mine}
tabIndex={0}
role='button'
aria-label={t('React_with__reaction__', { reaction: name })}
// if data-tooltip is not set, the tooltip will close on first mouse enter
data-tooltip=''
onMouseEnter={async (e) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type MessageListContextValue = {
useShowFollowing: ({ message }: { message: IMessage }) => boolean;
useMessageDateFormatter: () => (date: Date) => string;
useUserHasReacted: (message: IMessage) => (reaction: string) => boolean;
useOpenEmojiPicker: (message: IMessage) => (event: React.MouseEvent) => void;
useOpenEmojiPicker: (message: IMessage) => (event: React.MouseEvent | React.KeyboardEvent) => void;
showRoles: boolean;
showRealName: boolean;
showUsername: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IMessage } from '@rocket.chat/core-typings';
import { Message, MessageLeftContainer, MessageContainer, CheckBox } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
import { MessageAvatar } from '@rocket.chat/ui-avatar';
import { useUserId } from '@rocket.chat/ui-contexts';
import { useTranslation, useUserId } from '@rocket.chat/ui-contexts';
import type { ComponentProps, ReactElement } from 'react';
import React, { memo } from 'react';

Expand Down Expand Up @@ -47,6 +47,7 @@ const RoomMessage = ({
searchText,
...props
}: RoomMessageProps): ReactElement => {
const t = useTranslation();
const uid = useUserId();
const editing = useIsMessageHighlight(message._id);
const [displayIgnoredMessage, toggleDisplayIgnoredMessage] = useToggle(false);
Expand All @@ -66,7 +67,9 @@ const RoomMessage = ({
ref={messageRef}
id={message._id}
role='listitem'
aria-roledescription={sequential ? t('sequential_message') : t('message')}
tabIndex={0}
aria-labelledby={`${message._id}-displayName ${message._id}-time ${message._id}-content`}
onClick={selecting ? toggleSelected : undefined}
isSelected={selected}
isEditing={editing}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const SystemMessage = ({ message, showUserAvatar, ...props }: SystemMessageProps
return (
<MessageSystem
role='listitem'
aria-roledescription={t('system_message')}
tabIndex={0}
onClick={isSelecting ? toggleSelected : undefined}
isSelected={isSelected}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type IThreadMessage, type IThreadMainMessage, isVideoConfMessage } from
import { Message, MessageLeftContainer, MessageContainer } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
import { MessageAvatar } from '@rocket.chat/ui-avatar';
import { useUserId } from '@rocket.chat/ui-contexts';
import { useTranslation, useUserId } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { memo } from 'react';

Expand All @@ -25,6 +25,7 @@ type ThreadMessageProps = {
};

const ThreadMessage = ({ message, sequential, unread, showUserAvatar }: ThreadMessageProps): ReactElement => {
const t = useTranslation();
const uid = useUserId();
const editing = useIsMessageHighlight(message._id);
const [ignored, toggleIgnoring] = useToggle((message as { ignored?: boolean }).ignored);
Expand All @@ -38,6 +39,7 @@ const ThreadMessage = ({ message, sequential, unread, showUserAvatar }: ThreadMe
return (
<Message
role='listitem'
aria-roledescription={t('thread_message')}
tabIndex={0}
id={message._id}
ref={messageRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,13 @@ const ThreadMessagePreview = ({ message, showUserAvatar, sequential, ...props }:

return (
<ThreadMessage
role='link'
aria-roledescription='thread message preview'
tabIndex={0}
onClick={handleThreadClick}
onKeyDown={(e) => e.code === 'Enter' && handleThreadClick()}
isSelected={isSelected}
data-qa-selected={isSelected}
role='link'
{...props}
>
{!sequential && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const RoomMessageContent = ({ message, unread, all, mention, searchText }: RoomM
<>
{(!encrypted || normalizedMessage.e2e === 'done') && (
<MessageContentBody
id={`${message._id}-content`}
md={normalizedMessage.md}
mentions={normalizedMessage.mentions}
channels={normalizedMessage.channels}
Expand Down
21 changes: 12 additions & 9 deletions apps/meteor/client/views/room/MessageList/MessageListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const MessageListItem = ({
<Box
ref={ref}
data-id={message.ts}
role='listitem'
{...(newDay && {
'data-time': new Date(message.ts)
.toISOString()
Expand Down Expand Up @@ -78,15 +79,17 @@ export const MessageListItem = ({
/>
)}
{isThreadMessage(message) && (
<ThreadMessagePreview
data-mid={message._id}
data-tmid={message.tmid}
data-unread={showUnreadDivider}
data-sequential={sequential}
sequential={shouldShowAsSequential}
message={message}
showUserAvatar={showUserAvatar}
/>
<li>
<ThreadMessagePreview
data-mid={message._id}
data-tmid={message.tmid}
data-unread={showUnreadDivider}
data-sequential={sequential}
sequential={shouldShowAsSequential}
message={message}
showUserAvatar={showUserAvatar}
/>
</li>
)}
{system && <SystemMessage showUserAvatar={showUserAvatar} message={message} />}
</>
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/views/room/body/RoomBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const RoomBody = (): ReactElement => {

const { innerRef: restoreScrollPositionInnerRef } = useRestoreScrollPosition(room._id);

const { messageListRef, messageListProps } = useMessageListNavigation();
const { messageListRef } = useMessageListNavigation();

const { handleNewMessageButtonClick, handleJumpToRecentButtonClick, handleComposerResize, hasNewMessages, newMessagesScrollRef } =
useHasNewMessages(room._id, user?._id, atBottomRef, {
Expand Down Expand Up @@ -283,7 +283,7 @@ const RoomBody = (): ReactElement => {
>
<MessageListErrorBoundary>
<CustomScrollbars ref={innerRef}>
<ul className='messages-list' aria-live='polite' aria-busy={isLoadingMoreMessages} {...messageListProps}>
<ul className='messages-list' aria-label={t('Message_list')} aria-busy={isLoadingMoreMessages}>
{canPreview ? (
<>
{hasMorePreviousMessages ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const MessageBoxFormattingToolbar = ({ items, variant = 'large', composer, disab
isPromptButton(featuredFormatter) ? featuredFormatter.prompt(composer) : composer.wrapSelection(featuredFormatter.pattern)
}
icon={featuredFormatter.icon}
title={t(featuredFormatter.label)}
disabled={disabled}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const ThreadMessageItem = ({
<>
{showDivider && (
<Box
role='listitem'
ref={ref}
data-id={message.ts}
{...(newDay && {
Expand All @@ -57,13 +58,11 @@ export const ThreadMessageItem = ({
</MessageDivider>
</Box>
)}
<li>
{system ? (
<SystemMessage message={message} showUserAvatar={showUserAvatar} />
) : (
<ThreadMessage message={message} sequential={shouldShowAsSequential} unread={firstUnread} showUserAvatar={showUserAvatar} />
)}
</li>
{system ? (
<SystemMessage message={message} showUserAvatar={showUserAvatar} />
) : (
<ThreadMessage message={message} sequential={shouldShowAsSequential} unread={firstUnread} showUserAvatar={showUserAvatar} />
)}
</>
);
};
Loading