{
+ setSelectedMessage(null);
+ }}
+ >
{userLoadedBackUntilPage ? (
{
@@ -301,6 +377,9 @@ const ChatMessageList: React.FC = ({
chatId={chatId}
markMessageAsSeen={markMessageAsSeen}
scrollRef={messageListContainerRefProxy}
+ focusedId={selectedMessage?.dataset.sequence
+ ? Number.parseInt(selectedMessage?.dataset.sequence, 10)
+ : null}
/>
);
})}
@@ -311,10 +390,13 @@ const ChatMessageList: React.FC = ({
height: 1,
background: 'none',
}}
+ tabIndex={-1}
+ aria-hidden
/>
,
renderUnreadNotification,
- ,
+ ,
+ ,
]
}
>
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/component.tsx
index 49b7bf2a41..182fd514ba 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/component.tsx
@@ -19,7 +19,9 @@ import {
MessageItemWrapper,
Container,
DeleteMessage,
- ChatEditTime,
+ ChatHeading,
+ EditLabel,
+ EditLabelWrapper,
} from './styles';
import { ChatEvents, ChatMessageType } from '/imports/ui/core/enums/chat';
import MessageReadConfirmation from './message-read-confirmation/component';
@@ -27,7 +29,6 @@ import ChatMessageToolbar from './message-toolbar/component';
import ChatMessageReactions from './message-reactions/component';
import ChatMessageReplied from './message-replied/component';
import { STORAGES, useStorageKey } from '/imports/ui/services/storage/hooks';
-import ChatEditMessageForm from './message-edit-form/component';
import useMeeting from '/imports/ui/core/hooks/useMeeting';
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
import { layoutSelect } from '/imports/ui/components/layout/context';
@@ -36,6 +37,15 @@ import useChat from '/imports/ui/core/hooks/useChat';
import { GraphqlDataHookSubscriptionResponse } from '/imports/ui/Types/hook';
import { Chat } from '/imports/ui/Types/chat';
import { CHAT_DELETE_REACTION_MUTATION, CHAT_SEND_REACTION_MUTATION } from './mutations';
+import Icon from '/imports/ui/components/common/icon/component';
+import { colorBlueLightestChannel } from '/imports/ui/stylesheets/styled-components/palette';
+import {
+ useIsReplyChatMessageEnabled,
+ useIsChatMessageReactionsEnabled,
+ useIsEditChatMessageEnabled,
+ useIsDeleteChatMessageEnabled,
+} from '/imports/ui/services/features';
+import ChatMessageNotificationContent from './message-content/notification-content/component';
interface ChatMessageProps {
message: Message;
@@ -45,6 +55,8 @@ interface ChatMessageProps {
scrollRef: React.RefObject;
markMessageAsSeen: (message: Message) => void;
messageReadFeedbackEnabled: boolean;
+ focused: boolean;
+ keyboardFocused: boolean;
}
const intlMessages = defineMessages({
@@ -76,6 +88,10 @@ const intlMessages = defineMessages({
id: 'app.chat.deleteMessage',
description: '',
},
+ edited: {
+ id: 'app.chat.toolbar.edit.edited',
+ description: 'edited message label',
+ },
});
function isInViewport(el: HTMLDivElement) {
@@ -98,6 +114,8 @@ const ChatMesssage: React.FC = ({
setMessagesRequestedFromPlugin,
markMessageAsSeen,
messageReadFeedbackEnabled,
+ focused,
+ keyboardFocused,
}) => {
const idChatOpen: string = layoutSelect((i: Layout) => i.idChatOpen);
const { data: meeting } = useMeeting((m) => ({
@@ -122,7 +140,6 @@ const ChatMesssage: React.FC = ({
}, [message, messageRef]);
const messageContentRef = React.createRef();
const [editing, setEditing] = React.useState(false);
- const [isToolbarMenuOpen, setIsToolbarMenuOpen] = React.useState(false);
const [isToolbarReactionPopoverOpen, setIsToolbarReactionPopoverOpen] = React.useState(false);
const chatFocusMessageRequest = useStorageKey(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, STORAGES.IN_MEMORY);
const containerRef = React.useRef(null);
@@ -150,7 +167,6 @@ const ChatMesssage: React.FC = ({
});
}, []);
- const CHAT_TOOLBAR_CONFIG = window.meetingClientSettings.public.chat.toolbar;
const isModerator = currentUser?.isModerator;
const isPublicChat = chat?.public;
const isLocked = currentUser?.locked || currentUser?.userLockSettings?.disablePublicChat;
@@ -168,11 +184,17 @@ const ChatMesssage: React.FC = ({
}
}
- const hasToolbar = CHAT_TOOLBAR_CONFIG.length > 0
- && message.deletedAt === null
- && !editing
- && !!message.user
- && !locked;
+ const CHAT_REPLY_ENABLED = useIsReplyChatMessageEnabled();
+ const CHAT_REACTIONS_ENABLED = useIsChatMessageReactionsEnabled();
+ const CHAT_EDIT_ENABLED = useIsEditChatMessageEnabled();
+ const CHAT_DELETE_ENABLED = useIsDeleteChatMessageEnabled();
+
+ const hasToolbar = !!message.user && [
+ CHAT_REPLY_ENABLED,
+ CHAT_REACTIONS_ENABLED,
+ CHAT_EDIT_ENABLED,
+ CHAT_DELETE_ENABLED,
+ ].some((config) => config);
const startScrollAnimation = (timestamp: number) => {
if (scrollRef.current && containerRef.current) {
@@ -184,7 +206,7 @@ const ChatMesssage: React.FC = ({
};
useEffect(() => {
- const handler = (e: Event) => {
+ const handleFocusMessageRequest = (e: Event) => {
if (e instanceof CustomEvent) {
if (e.detail.sequence === message.messageSequence) {
requestAnimationFrame(startScrollAnimation);
@@ -192,10 +214,27 @@ const ChatMesssage: React.FC = ({
}
};
- window.addEventListener(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, handler);
+ const handleChatEditRequest = (e: Event) => {
+ if (e instanceof CustomEvent) {
+ const editing = e.detail.messageId === message.messageId;
+ setEditing(editing);
+ }
+ };
+
+ const handleCancelChatEditRequest = (e: Event) => {
+ if (e instanceof CustomEvent) {
+ setEditing(false);
+ }
+ };
+
+ window.addEventListener(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, handleFocusMessageRequest);
+ window.addEventListener(ChatEvents.CHAT_EDIT_REQUEST, handleChatEditRequest);
+ window.addEventListener(ChatEvents.CHAT_CANCEL_EDIT_REQUEST, handleCancelChatEditRequest);
return () => {
- window.removeEventListener(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, handler);
+ window.removeEventListener(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, handleFocusMessageRequest);
+ window.removeEventListener(ChatEvents.CHAT_EDIT_REQUEST, handleChatEditRequest);
+ window.removeEventListener(ChatEvents.CHAT_CANCEL_EDIT_REQUEST, handleCancelChatEditRequest);
};
}, []);
@@ -209,7 +248,7 @@ const ChatMesssage: React.FC = ({
if (!containerRef.current) return;
const value = (timestamp - animationInitialTimestamp.current) / ANIMATION_DURATION;
if (value < 1) {
- containerRef.current.style.backgroundColor = `rgba(243, 246, 249, ${1 - value})`;
+ containerRef.current.style.backgroundColor = `rgb(${colorBlueLightestChannel} / ${1 - value})`;
requestAnimationFrame(animate);
} else {
containerRef.current.style.backgroundColor = 'unset';
@@ -258,6 +297,7 @@ const ChatMesssage: React.FC = ({
const formattedTime = intl.formatTime(dateTime, {
hour: 'numeric',
minute: 'numeric',
+ hour12: false,
});
const editTime = message.editedAt ? new Date(message.editedAt) : null;
const deleteTime = message.deletedAt ? new Date(message.deletedAt) : null;
@@ -266,12 +306,15 @@ const ChatMesssage: React.FC = ({
const clearMessage = `${msgTime} ${intl.formatMessage(intlMessages.chatClear)}`;
const messageContent: {
- name: string,
- color: string,
- isModerator: boolean,
- isPresentationUpload?: boolean,
- component: React.ReactElement,
- avatarIcon?: string,
+ name: string;
+ color: string;
+ isModerator: boolean;
+ isPresentationUpload?: boolean;
+ component: React.ReactNode;
+ avatarIcon?: string;
+ isSystemSender?: boolean;
+ showAvatar: boolean;
+ showHeading: boolean;
} = useMemo(() => {
switch (message.messageType) {
case ChatMessageType.POLL:
@@ -283,6 +326,8 @@ const ChatMesssage: React.FC = ({
),
avatarIcon: 'icon-bbb-polling',
+ showAvatar: true,
+ showHeading: true,
};
case ChatMessageType.PRESENTATION:
return {
@@ -296,6 +341,8 @@ const ChatMesssage: React.FC = ({
/>
),
avatarIcon: 'icon-bbb-download',
+ showAvatar: true,
+ showHeading: true,
};
case ChatMessageType.CHAT_CLEAR:
return {
@@ -304,26 +351,28 @@ const ChatMesssage: React.FC = ({
isModerator: false,
isSystemSender: true,
component: (
-
),
+ showAvatar: false,
+ showHeading: false,
};
case ChatMessageType.BREAKOUT_ROOM:
return {
name: message.senderName,
color: '#0F70D7',
isModerator: true,
- isSystemSender: true,
+ isSystemSender: false,
component: (
),
+ showAvatar: true,
+ showHeading: true,
};
case ChatMessageType.API:
return {
@@ -332,30 +381,31 @@ const ChatMesssage: React.FC = ({
isModerator: true,
isSystemSender: true,
component: (
-
),
+ showAvatar: false,
+ showHeading: false,
};
case ChatMessageType.USER_AWAY_STATUS_MSG: {
const { away } = JSON.parse(message.messageMetadata);
const awayMessage = (away)
- ? `${intl.formatMessage(intlMessages.userAway)}`
- : `${intl.formatMessage(intlMessages.userNotAway)}`;
+ ? intl.formatMessage(intlMessages.userAway, { user: message.senderName })
+ : intl.formatMessage(intlMessages.userNotAway, { user: message.senderName });
return {
name: message.senderName,
color: '#0F70D7',
isModerator: true,
isSystemSender: true,
component: (
-
),
+ showAvatar: false,
+ showHeading: false,
};
}
case ChatMessageType.PLUGIN: {
@@ -364,13 +414,14 @@ const ChatMesssage: React.FC = ({
color: message.user?.color,
isModerator: message.user?.isModerator,
isSystemSender: false,
+ showAvatar: true,
+ showHeading: true,
component: currentPluginMessageMetadata.custom
- ? (<>>)
+ ? null
: (
),
};
@@ -381,104 +432,115 @@ const ChatMesssage: React.FC = ({
name: message.user?.name,
color: message.user?.color,
isModerator: message.user?.isModerator,
- isSystemSender: ChatMessageType.BREAKOUT_ROOM,
+ isSystemSender: false,
+ showAvatar: true,
+ showHeading: true,
component: (
),
};
}
}, [message.message]);
+
+ const shouldRenderAvatar = messageContent.showAvatar
+ && !sameSender
+ && !isCustomPluginMessage;
+
+ const shouldRenderHeader = messageContent.showHeading
+ && !sameSender
+ && !isCustomPluginMessage;
+
return (
-
+
- {hasToolbar && (
- {
- sendReaction(emoji.native);
- setIsToolbarReactionPopoverOpen(false);
- }}
- onEditRequest={() => {
- setEditing(true);
- }}
- onMenuOpenChange={setIsToolbarMenuOpen}
- menuIsOpen={isToolbarMenuOpen}
- onReactionPopoverOpenChange={setIsToolbarReactionPopoverOpen}
- reactionPopoverIsOpen={isToolbarReactionPopoverOpen}
- />
- )}
- {((!message?.user || !sameSender) && (
- message.messageType !== ChatMessageType.USER_AWAY_STATUS_MSG
- && message.messageType !== ChatMessageType.API
- && message.messageType !== ChatMessageType.CHAT_CLEAR
- && !isCustomPluginMessage)
- ) && (
-
- {!messageContent.avatarIcon ? (
- !message.user || (message.user?.avatar.length === 0 ? messageContent.name.toLowerCase().slice(0, 2) : '')
- ) : (
-
+ {
+ sendReaction(emoji.native);
+ setIsToolbarReactionPopoverOpen(false);
+ }}
+ onReactionPopoverOpenChange={setIsToolbarReactionPopoverOpen}
+ reactionPopoverIsOpen={isToolbarReactionPopoverOpen}
+ chatDeleteEnabled={CHAT_DELETE_ENABLED}
+ chatEditEnabled={CHAT_EDIT_ENABLED}
+ chatReactionsEnabled={CHAT_REACTIONS_ENABLED}
+ chatReplyEnabled={CHAT_REPLY_ENABLED}
+ />
+ {(shouldRenderAvatar || shouldRenderHeader) && (
+
+ {shouldRenderAvatar && (
+
+ {!messageContent.avatarIcon ? (
+ !message.user || (message.user?.avatar.length === 0 ? messageContent.name.toLowerCase().slice(0, 2) : '')
+ ) : (
+
+ )}
+
)}
-
- )}
- {!editing && (
-
- {message.messageType !== ChatMessageType.CHAT_CLEAR
- && !isCustomPluginMessage
- && (
+ {shouldRenderHeader && (
)}
- {message.replyToMessage && (
+
+ )}
+
+ {message.replyToMessage && !deleteTime && (
)}
{!deleteTime && (
-
+
{messageContent.component}
{messageReadFeedbackEnabled && (
= ({
)}
)}
- {!deleteTime && editTime && (
-
- {`(${intl.formatMessage(intlMessages.editTime, { 0: intl.formatTime(editTime) })})`}
-
+ {!deleteTime && editTime && sameSender && (
+
+
+
+ {intl.formatMessage(intlMessages.edited)}
+
+
)}
{deleteTime && (
{intl.formatMessage(intlMessages.deleteMessage, { 0: message.deletedBy?.name })}
)}
- {!deleteTime && (
-
- )}
- )}
- {editing && (
- setEditing(false)}
- onAfterSubmit={() => setEditing(false)}
- sameSender={message?.user ? sameSender : false}
- />
+ {!deleteTime && (
+
)}
@@ -528,7 +582,9 @@ function areChatMessagesEqual(prevProps: ChatMessageProps, nextProps: ChatMessag
&& prevMessage?.user?.currentlyInMeeting === nextMessage?.user?.currentlyInMeeting
&& prevMessage?.recipientHasSeen === nextMessage.recipientHasSeen
&& prevMessage?.message === nextMessage.message
- && prevMessage?.reactions?.length === nextMessage?.reactions?.length;
+ && prevMessage?.reactions?.length === nextMessage?.reactions?.length
+ && prevProps.focused === nextProps.focused
+ && prevProps.keyboardFocused === nextProps.keyboardFocused;
}
export default memo(ChatMesssage, areChatMessagesEqual);
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/notification-content/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/notification-content/component.tsx
new file mode 100644
index 0000000000..491b5caf4e
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/notification-content/component.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import Styled from './styles';
+
+interface ChatMessageNotificationContentProps {
+ text: string;
+ iconName?: string;
+}
+
+const ChatMessageNotificationContent: React.FC = (props) => {
+ const { text, iconName } = props;
+ return (
+
+ {iconName && }
+
+ {text}
+
+
+ );
+};
+
+export default ChatMessageNotificationContent;
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/notification-content/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/notification-content/styles.ts
new file mode 100644
index 0000000000..642030d66c
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/notification-content/styles.ts
@@ -0,0 +1,35 @@
+import styled from 'styled-components';
+import BaseIcon from '/imports/ui/components/common/icon/component';
+import { colorGray } from '/imports/ui/stylesheets/styled-components/palette';
+import { $3xlPadding, smPadding } from '/imports/ui/stylesheets/styled-components/general';
+
+export const Root = styled.div`
+ color: ${colorGray};
+ padding: 0 ${$3xlPadding};
+ width: 100%;
+ text-align: center;
+`;
+
+export const Icon = styled(BaseIcon)`
+ vertical-align: baseline;
+
+ [dir='ltr'] & {
+ margin-right: ${smPadding};
+ }
+
+ [dir='rtl'] & {
+ margin-left: ${smPadding};
+ }
+`;
+
+export const Typography = styled.span`
+ vertical-align: baseline;
+ overflow-wrap: break-word;
+ white-space: pre-wrap;
+`;
+
+export default {
+ Root,
+ Icon,
+ Typography,
+};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx
index 8610f1ade1..5dd5e322dc 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx
@@ -5,19 +5,17 @@ import Styled from './styles';
interface ChatMessageTextContentProps {
text: string;
emphasizedMessage: boolean;
- systemMsg: boolean;
dataTest?: string | null;
}
const ChatMessageTextContent: React.FC = ({
text,
emphasizedMessage,
- systemMsg,
dataTest = 'messageContent',
}) => {
const { allowedElements } = window.meetingClientSettings.public.chat;
return (
-
+
`
flex-direction: column;
color: ${colorText};
word-break: break-word;
- ${({ systemMsg }) => systemMsg && `
- background: ${systemMessageBackgroundColor};
- border: 1px solid ${systemMessageBorderColor};
- border-radius: ${borderRadius};
- font-weight: ${btnFontWeight};
- padding: ${fontSizeBase};
- text-color: #1f252b;
- margin-top: 0;
- margin-bottom: 0;
- overflow-wrap: break-word;
- `}
+
${({ emphasizedMessage }) => emphasizedMessage && `
font-weight: bold;
`}
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-edit-form/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-edit-form/component.tsx
deleted file mode 100644
index 2202c46483..0000000000
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-edit-form/component.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import React, { useEffect } from 'react';
-import { defineMessages, useIntl } from 'react-intl';
-import { checkText } from 'smile2emoji';
-import { useMutation } from '@apollo/client';
-import Button from '/imports/ui/components/common/button/component';
-import { textToMarkdown } from '/imports/ui/components/chat/chat-graphql/chat-message-form/service';
-import { CHAT_EDIT_MESSAGE_MUTATION } from '../mutations';
-import Styled from './styles';
-import logger from '/imports/startup/client/logger';
-
-const intlMessages = defineMessages({
- errorMaxMessageLength: {
- id: 'app.chat.errorMaxMessageLength',
- },
- errorOnUpdateMessage: {
- id: 'app.chat.errorOnUpdateMessage',
- },
-});
-
-interface ChatEditMessageFormProps {
- onCancel(): void;
- onAfterSubmit(): void;
- initialMessage: string;
- chatId: string;
- messageId: string;
- sameSender: boolean;
-}
-
-const ChatEditMessageForm: React.FC = (props) => {
- const {
- onCancel, chatId, initialMessage, messageId, onAfterSubmit, sameSender,
- } = props;
- const [editedMessage, setEditedMessage] = React.useState(initialMessage);
- const [hasErrors, setHasErrors] = React.useState(false);
- const [error, setError] = React.useState(null);
- const intl = useIntl();
-
- const [chatEditMessage, {
- loading: chatEditMessageLoading, error: chatEditMessageError,
- }] = useMutation(CHAT_EDIT_MESSAGE_MUTATION);
-
- const CHAT_CONFIG = window.meetingClientSettings.public.chat;
- const AUTO_CONVERT_EMOJI = CHAT_CONFIG.autoConvertEmoji;
- const MIN_MESSAGE_LENGTH = CHAT_CONFIG.min_message_length;
- const MAX_MESSAGE_LENGTH = CHAT_CONFIG.max_message_length;
- const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
- const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
-
- useEffect(() => {
- if (chatEditMessageError && error == null) {
- logger.error({
- logCode: 'update_message_error',
- extraInfo: {
- errorName: chatEditMessageError.name,
- errorMessage: chatEditMessageError.message,
- },
- }, 'Updating chat message failed');
- setError(intl.formatMessage(intlMessages.errorOnUpdateMessage));
- }
- }, [chatEditMessageError]);
-
- const handleMessageChange: React.ChangeEventHandler = (e) => {
- let newMessage = null;
- let newError = null;
- if (AUTO_CONVERT_EMOJI) {
- newMessage = checkText(e.target.value);
- } else {
- newMessage = e.target.value;
- }
-
- if (newMessage.length > MAX_MESSAGE_LENGTH) {
- newError = intl.formatMessage(
- intlMessages.errorMaxMessageLength,
- { 0: MAX_MESSAGE_LENGTH },
- );
- newMessage = newMessage.substring(0, MAX_MESSAGE_LENGTH);
- }
- setEditedMessage(newMessage);
- setError(newError);
- };
-
- const handleSubmit = (e: React.FormEvent | React.KeyboardEvent | Event) => {
- e.preventDefault();
-
- const msg = textToMarkdown(editedMessage);
-
- if (msg.length < MIN_MESSAGE_LENGTH || chatEditMessageLoading) return;
-
- if (msg.length > MAX_MESSAGE_LENGTH) {
- setHasErrors(true);
- return;
- }
-
- if (!chatEditMessageLoading) {
- chatEditMessage({
- variables: {
- chatMessageInMarkdownFormat: msg,
- chatId: chatId === PUBLIC_CHAT_ID ? PUBLIC_GROUP_CHAT_ID : chatId,
- messageId,
- },
- });
- }
-
- setError(null);
- setHasErrors(false);
-
- onAfterSubmit();
- };
-
- const handleMessageKeyDown: React.KeyboardEventHandler = (e) => {
- if (e.code === 'Enter' && !e.shiftKey) {
- e.preventDefault();
-
- const event = new Event('submit', {
- bubbles: true,
- cancelable: true,
- });
- handleSubmit(event);
- }
- };
-
- return (
-
- );
-};
-
-export default ChatEditMessageForm;
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-edit-form/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-edit-form/styles.ts
deleted file mode 100644
index 8333e4456b..0000000000
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-edit-form/styles.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import styled from 'styled-components';
-import { borderSize } from '/imports/ui/stylesheets/styled-components/general';
-import { colorDanger, colorGrayDark } from '/imports/ui/stylesheets/styled-components/palette';
-import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
-
-const Actions = styled.div`
- display: flex;
- justify-content: flex-end;
- padding-top: 8px;
-`;
-
-const Section = styled.section<{ $sameSender: boolean }>`
- ${({ $sameSender }) => $sameSender && `
- margin-left: 2.6rem;
- `}
-`;
-
-const ChatMessageError = styled.div`
- color: ${colorDanger};
- font-size: calc(${fontSizeBase} * .75);
- color: ${colorGrayDark};
- text-align: left;
- padding: ${borderSize} 0;
- word-break: break-word;
- position: relative;
- margin-right: 0.05rem;
- margin-left: 0.05rem;
-`;
-
-export default {
- Actions,
- Section,
- ChatMessageError,
-};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx
index 955eb9883f..2e684ca1d0 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { useIntl, defineMessages, FormattedTime } from 'react-intl';
+import Icon from '/imports/ui/components/common/icon/component';
import Styled from './styles';
const intlMessages = defineMessages({
@@ -7,6 +8,10 @@ const intlMessages = defineMessages({
id: 'app.chat.offline',
description: 'Offline',
},
+ edited: {
+ id: 'app.chat.toolbar.edit.edited',
+ description: 'Edited',
+ },
});
interface ChatMessageHeaderProps {
@@ -15,6 +20,7 @@ interface ChatMessageHeaderProps {
dateTime: Date;
sameSender: boolean;
deleteTime: Date | null;
+ editTime: Date | null;
}
const ChatMessageHeader: React.FC = ({
@@ -23,6 +29,7 @@ const ChatMessageHeader: React.FC = ({
currentlyInMeeting,
dateTime,
deleteTime,
+ editTime,
}) => {
const intl = useIntl();
if (sameSender) return null;
@@ -40,11 +47,20 @@ const ChatMessageHeader: React.FC = ({
)
}
- {!deleteTime && (
-
-
-
+ {!deleteTime && editTime && (
+
+
+ {intl.formatMessage(intlMessages.edited)}
+
)}
+ {deleteTime && (
+
+
+
+ )}
+
+
+
);
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts
index bf20cee5e9..bb406ce45c 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts
@@ -2,10 +2,10 @@ import styled from 'styled-components';
import {
colorHeading,
- palettePlaceholderText,
colorGrayLight,
+ colorGrayDark,
} from '/imports/ui/stylesheets/styled-components/palette';
-import { lineHeightComputed } from '/imports/ui/stylesheets/styled-components/typography';
+import { fontSizeSmaller, lineHeightComputed } from '/imports/ui/stylesheets/styled-components/typography';
interface ChatUserNameProps {
currentlyInMeeting: boolean;
@@ -14,6 +14,7 @@ interface ChatUserNameProps {
export const HeaderContent = styled.div`
display: flex;
flex-flow: row;
+ align-items: center;
width: 100%;
`;
@@ -30,6 +31,7 @@ export const ChatUserName = styled.div`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ flex-grow: 1;
${({ currentlyInMeeting }) => currentlyInMeeting && `
color: ${colorHeading};
@@ -65,8 +67,8 @@ export const ChatUserOffline = styled.span`
export const ChatTime = styled.time`
flex-shrink: 0;
flex-grow: 0;
- flex-basis: 3.5rem;
- color: ${palettePlaceholderText};
+ flex-basis: max-content;
+ color: ${colorGrayDark};
text-transform: uppercase;
font-size: 75%;
[dir='rtl'] & {
@@ -84,10 +86,27 @@ export const ChatHeaderText = styled.div`
width: 100%;
`;
+export const EditLabel = styled.span`
+ color: ${colorGrayLight};
+ font-size: ${fontSizeSmaller};
+ display: flex;
+ align-items: center;
+ gap: calc(${lineHeightComputed} / 4);
+
+ [dir='ltr'] & {
+ margin-right: calc(${lineHeightComputed} / 2);
+ }
+
+ [dir='rtl'] & {
+ margin-left: calc(${lineHeightComputed} / 2);
+ }
+`;
+
export default {
HeaderContent,
ChatTime,
ChatUserOffline,
ChatUserName,
ChatHeaderText,
+ EditLabel,
};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/component.tsx
index 4a90c354f5..5020e4c87d 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/component.tsx
@@ -38,6 +38,8 @@ const ChatMessageReactions: React.FC = (props) => {
const { data: currentUser } = useCurrentUser((u) => ({ userId: u.userId }));
const intl = useIntl();
+ if (reactions.length === 0) return null;
+
const reactionItems: Record = {};
reactions.forEach((reaction) => {
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/styles.ts
index fb060c9e95..38dec730a6 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/styles.ts
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-reactions/styles.ts
@@ -1,29 +1,41 @@
import styled from 'styled-components';
-import { colorBlueLighter, colorBlueLightest, colorGray } from '/imports/ui/stylesheets/styled-components/palette';
+import {
+ colorGrayLighter, colorGrayLightest, colorOffWhite,
+} from '/imports/ui/stylesheets/styled-components/palette';
const EmojiWrapper = styled.button<{ highlighted: boolean }>`
- background-color: ${colorBlueLightest};
- border-radius: 10px;
- margin-left: 3px;
- margin-top: 3px;
- padding: 3px;
+ background: none;
+ border-radius: 1rem;
+ padding: 0.375rem 1rem;
+ line-height: 1;
display: flex;
flex-wrap: nowrap;
- border: 1px solid transparent;
+ border: 1px solid ${colorGrayLightest};
cursor: pointer;
${({ highlighted }) => highlighted && `
- background-color: ${colorBlueLighter};
+ background-color: ${colorOffWhite};
`}
+ em-emoji {
+ [dir='ltr'] & {
+ margin-right: 0.25rem;
+ }
+
+ [dir='rtl'] & {
+ margin-left: 0.25rem;
+ }
+ }
+
&:hover {
- border: 1px solid ${colorGray};
+ border: 1px solid ${colorGrayLighter};
}
`;
const ReactionsWrapper = styled.div`
display: flex;
flex-wrap: wrap;
+ gap: 0.25rem;
`;
export default {
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/component.tsx
index c8ab65ee4f..e8b32b0507 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/component.tsx
@@ -13,24 +13,21 @@ const intlMessages = defineMessages({
});
interface MessageRepliedProps {
- username: string;
message: string;
sequence: number;
- userColor: string;
emphasizedMessage: boolean;
deletedByUser: string | null;
}
const ChatMessageReplied: React.FC = (props) => {
const {
- message, username, sequence, userColor, emphasizedMessage, deletedByUser,
+ message, sequence, emphasizedMessage, deletedByUser,
} = props;
const intl = useIntl();
return (
{
window.dispatchEvent(
new CustomEvent(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, {
@@ -43,13 +40,11 @@ const ChatMessageReplied: React.FC = (props) => {
Storage.setItem(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, sequence);
}}
>
- {username}
{!deletedByUser && (
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/styles.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/styles.tsx
index 8da726621c..8c64afc611 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/styles.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-replied/styles.tsx
@@ -1,24 +1,35 @@
import styled from 'styled-components';
-import { colorOffWhite, colorText } from '/imports/ui/stylesheets/styled-components/palette';
+import {
+ colorGrayLightest, colorOffWhite, colorPrimary, colorText,
+} from '/imports/ui/stylesheets/styled-components/palette';
+import { $3xlPadding, lgPadding } from '/imports/ui/stylesheets/styled-components/general';
-const Container = styled.div<{ $userColor: string }>`
- border-radius: 4px;
- border-left: 4px solid ${({ $userColor }) => $userColor};
+const Container = styled.div`
+ border-top-left-radius: 0.5rem;
+ border-top-right-radius: 0.5rem;
background-color: ${colorOffWhite};
- padding: 6px;
+ box-shadow: inset 0 0 0 1px ${colorGrayLightest};
+ padding: ${lgPadding} ${$3xlPadding};
position: relative;
- margin: 0.25rem 0 0.25rem 0;
overflow: hidden;
cursor: pointer;
+
+ [dir='ltr'] & {
+ border-right: 0.5rem solid ${colorPrimary};
+ }
+
+ [dir='rtl'] & {
+ border-left: 0.5rem solid ${colorPrimary};
+ }
`;
const Typography = styled.div`
overflow: hidden;
`;
-const Username = styled(Typography)<{ $userColor: string }>`
+const Username = styled(Typography)`
font-weight: bold;
- color: ${({ $userColor }) => $userColor};
+ color: ${colorPrimary};
line-height: 1rem;
font-size: 1rem;
white-space: nowrap;
@@ -26,8 +37,8 @@ const Username = styled(Typography)<{ $userColor: string }>`
`;
const Message = styled(Typography)`
- max-height: 3.6rem;
- line-height: 1.2rem;
+ max-height: 1rem;
+ line-height: 1rem;
overflow: hidden;
`;
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/component.tsx
index 02ca9ff5f9..d5f6a72537 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/component.tsx
@@ -1,26 +1,21 @@
import React from 'react';
+import { defineMessages, useIntl } from 'react-intl';
import { useMutation } from '@apollo/client';
import Popover from '@mui/material/Popover';
+import { FocusTrap } from '@mui/base/FocusTrap';
import { layoutSelect } from '/imports/ui/components/layout/context';
-import Button from '/imports/ui/components/common/button/component';
-import BBBMenu from '/imports/ui/components/common/menu/component';
import { Layout } from '/imports/ui/components/layout/layoutTypes';
import { ChatEvents } from '/imports/ui/core/enums/chat';
-import { defineMessages, useIntl } from 'react-intl';
+import ConfirmationModal from '/imports/ui/components/common/modal/confirmation/component';
import {
Container,
+ Divider,
+ EmojiButton,
EmojiPicker,
EmojiPickerWrapper,
- EmojiButton,
+ Root,
} from './styles';
-import { colorDanger, colorWhite } from '/imports/ui/stylesheets/styled-components/palette';
import { CHAT_DELETE_MESSAGE_MUTATION } from '../mutations';
-import {
- useIsChatMessageReactionsEnabled,
- useIsDeleteChatMessageEnabled,
- useIsEditChatMessageEnabled,
- useIsReplyChatMessageEnabled,
-} from '/imports/ui/services/features';
const intlMessages = defineMessages({
reply: {
@@ -35,6 +30,18 @@ const intlMessages = defineMessages({
id: 'app.chat.toolbar.delete',
description: 'delete label',
},
+ cancelLabel: {
+ id: 'app.chat.toolbar.delete.cancelLabel',
+ description: '',
+ },
+ confirmationTitle: {
+ id: 'app.chat.toolbar.delete.confirmationTitle',
+ description: '',
+ },
+ confirmationDescription: {
+ id: 'app.chat.toolbar.delete.confimationDescription',
+ description: '',
+ },
});
interface ChatMessageToolbarProps {
@@ -47,198 +54,183 @@ interface ChatMessageToolbarProps {
messageSequence: number;
emphasizedMessage: boolean;
onEmojiSelected(emoji: { id: string; native: string }): void;
- onEditRequest(): void;
- onMenuOpenChange(open: boolean): void;
- menuIsOpen: boolean;
onReactionPopoverOpenChange(open: boolean): void;
reactionPopoverIsOpen: boolean;
+ hasToolbar: boolean;
+ locked: boolean;
+ deleted: boolean;
+ chatReplyEnabled: boolean;
+ chatReactionsEnabled: boolean;
+ chatEditEnabled: boolean;
+ chatDeleteEnabled: boolean;
+ keyboardFocused: boolean;
}
const ChatMessageToolbar: React.FC = (props) => {
const {
- messageId, chatId, message, username, onEmojiSelected, onMenuOpenChange,
- messageSequence, emphasizedMessage, onEditRequest, own, amIModerator, menuIsOpen,
- onReactionPopoverOpenChange, reactionPopoverIsOpen,
+ messageId, chatId, message, username, onEmojiSelected, deleted,
+ messageSequence, emphasizedMessage, own, amIModerator, locked,
+ onReactionPopoverOpenChange, reactionPopoverIsOpen, hasToolbar, keyboardFocused,
+ chatDeleteEnabled, chatEditEnabled, chatReactionsEnabled, chatReplyEnabled,
} = props;
const [reactionsAnchor, setReactionsAnchor] = React.useState(
null,
);
+ const [isTryingToDelete, setIsTryingToDelete] = React.useState(false);
const intl = useIntl();
const [chatDeleteMessage] = useMutation(CHAT_DELETE_MESSAGE_MUTATION);
const isRTL = layoutSelect((i: Layout) => i.isRTL);
- const CHAT_REPLIES_ENABLED = useIsReplyChatMessageEnabled();
- const CHAT_REACTIONS_ENABLED = useIsChatMessageReactionsEnabled();
- const CHAT_EDIT_ENABLED = useIsEditChatMessageEnabled();
- const CHAT_DELETE_ENABLED = useIsDeleteChatMessageEnabled();
- const actions = [];
-
- if (CHAT_EDIT_ENABLED && own) {
- actions.push({
- key: 'edit',
- icon: 'pen_tool',
- label: intl.formatMessage(intlMessages.edit),
- onClick: (e: React.MouseEvent) => {
- e.stopPropagation();
- onEditRequest();
- window.dispatchEvent(
- new CustomEvent(ChatEvents.CHAT_EDIT_REQUEST, {
- detail: {
- messageId,
- chatId,
- },
- }),
- );
- },
- });
- }
-
- if (CHAT_DELETE_ENABLED && (own || amIModerator)) {
- const customStyles = { background: colorDanger, color: colorWhite };
- actions.push({
- key: 'delete',
- icon: 'delete',
- label: intl.formatMessage(intlMessages.delete),
- customStyles,
- onClick: (e: React.MouseEvent) => {
- e.stopPropagation();
-
- chatDeleteMessage({
- variables: {
- chatId,
- messageId,
- },
- });
- },
- });
- }
if ([
- CHAT_REPLIES_ENABLED,
- CHAT_REACTIONS_ENABLED,
- CHAT_EDIT_ENABLED,
- CHAT_DELETE_ENABLED,
- ].every((config) => !config)) return null;
+ chatReplyEnabled,
+ chatReactionsEnabled,
+ chatEditEnabled,
+ chatDeleteEnabled,
+ ].every((config) => !config) || !hasToolbar || locked || deleted) return null;
+
+ const showReplyButton = chatReplyEnabled;
+ const showReactionsButton = chatReactionsEnabled;
+ const showEditButton = chatEditEnabled && own;
+ const showDeleteButton = chatDeleteEnabled && (own || amIModerator);
+ const showDivider = (showReplyButton || showReactionsButton) && (showEditButton || showDeleteButton);
return (
- {
+ if (e.key === 'Escape' && keyboardFocused) {
+ window.dispatchEvent(new CustomEvent(ChatEvents.CHAT_KEYBOARD_FOCUS_MESSAGE_CANCEL));
+ }
+ }}
>
- {CHAT_REPLIES_ENABLED && (
- <>
-
+
+
);
};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/emoji-button/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/emoji-button/component.tsx
new file mode 100644
index 0000000000..eaf9f05fc2
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/emoji-button/component.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import Styled from './styles';
+import Icon from '/imports/ui/components/common/icon/component';
+
+interface EmojiButtonProps extends React.ComponentProps<'button'> {
+ icon: string;
+}
+
+const EmojiButton = React.forwardRef((props, ref) => {
+ const {
+ icon,
+ ...buttonProps
+ } = props;
+
+ const IconComponent = ();
+
+ return (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ {IconComponent}
+
+ );
+});
+
+export default EmojiButton;
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/emoji-button/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/emoji-button/styles.ts
new file mode 100644
index 0000000000..23f825c182
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/emoji-button/styles.ts
@@ -0,0 +1,34 @@
+import styled from 'styled-components';
+import { colorGray } from '/imports/ui/stylesheets/styled-components/palette';
+import { lgPadding } from '/imports/ui/stylesheets/styled-components/general';
+import { fontSizeSmaller } from '/imports/ui/stylesheets/styled-components/typography';
+
+const EmojiButton = styled.button`
+ line-height: 1;
+ font-size: ${fontSizeSmaller};
+ background: none;
+ border: none;
+ outline: none;
+ padding: ${lgPadding};
+ color: ${colorGray};
+ cursor: pointer;
+
+ &:focus,
+ &:hover {
+ opacity: 0.5;
+ }
+
+ &:active {
+ transform: scale(0.9);
+ }
+
+ i::before {
+ display: block;
+ width: 100%;
+ height: 100%;
+ }
+`;
+
+export default {
+ EmojiButton,
+};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/styles.ts
index cda3c0c886..0efd3f838b 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/styles.ts
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-toolbar/styles.ts
@@ -1,44 +1,55 @@
import styled, { css } from 'styled-components';
import {
colorGrayLighter,
- colorOffWhite,
+ colorGrayLightest,
colorWhite,
} from '/imports/ui/stylesheets/styled-components/palette';
-import { borderRadius, smPaddingX } from '/imports/ui/stylesheets/styled-components/general';
+import { $2xlPadding, borderRadius, smPadding } from '/imports/ui/stylesheets/styled-components/general';
import EmojiPickerComponent from '/imports/ui/components/emoji-picker/component';
-import Button from '/imports/ui/components/common/button/component';
+import BaseEmojiButton from './emoji-button/component';
-const Container = styled.div<{ $sequence: number, $menuIsOpen: boolean, $reactionPopoverIsOpen: boolean }>`
- height: calc(1.5rem + 12px);
- line-height: calc(1.5rem + 8px);
- max-width: 184px;
- overflow: hidden;
+interface RootProps {
+ $reactionPopoverIsOpen: boolean;
+}
+
+const Root = styled.div`
+ padding-bottom: ${smPadding};
+ justify-content: flex-end;
display: none;
position: absolute;
- right: 0;
- border: 1px solid ${colorOffWhite};
- border-radius: 8px;
- padding: 1px;
- background-color: ${colorWhite};
- z-index: 2;
+ bottom: 100%;
+ z-index: 10;
- #chat-message-wrapper:hover & {
+ [dir='ltr'] & {
+ padding-left: ${$2xlPadding};
+ right: 0;
+ }
+
+ [dir='rtl'] & {
+ padding-right: ${$2xlPadding};
+ left: 0;
+ }
+
+ .chat-message-wrapper:hover &,
+ .chat-message-wrapper:focus &,
+ .chat-message-wrapper-focused &,
+ .chat-message-wrapper-keyboard-focused &,
+ &:hover,
+ &:focus-within {
display: flex;
}
- ${({ $menuIsOpen, $reactionPopoverIsOpen }) => (($menuIsOpen || $reactionPopoverIsOpen) && css`
+ ${({ $reactionPopoverIsOpen }) => ($reactionPopoverIsOpen && css`
display: flex;
`)}
+`;
- ${({ $sequence }) => (($sequence === 1)
- ? css`
- bottom: 0;
- transform: translateY(50%);
- `
- : css`
- top: 0;
- transform: translateY(-50%);
- `)}
+const Container = styled.div`
+ max-width: max-content;
+ display: flex;
+ border-radius: 1rem;
+ background-color: ${colorWhite};
+ box-shadow: 0 0.125rem 0.125rem 0 ${colorGrayLighter};
`;
const EmojiPickerWrapper = styled.div`
@@ -47,7 +58,7 @@ const EmojiPickerWrapper = styled.div`
right: 0;
border: 1px solid ${colorGrayLighter};
border-radius: ${borderRadius};
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ box-shadow: 0 0.125rem 10px rgba(0,0,0,0.1);
z-index: 1000;
.emoji-mart {
@@ -70,29 +81,25 @@ const EmojiPickerWrapper = styled.div`
}
`;
-// @ts-ignore
-const EmojiButton = styled(Button)`
- margin:0 0 0 ${smPaddingX};
- align-self: center;
- font-size: 0.5rem;
-
- [dir="rtl"] & {
- margin: 0 ${smPaddingX} 0 0;
- -webkit-transform: scale(-1, 1);
- -moz-transform: scale(-1, 1);
- -ms-transform: scale(-1, 1);
- -o-transform: scale(-1, 1);
- transform: scale(-1, 1);
- }
-`;
+const EmojiButton = styled(BaseEmojiButton)``;
const EmojiPicker = styled(EmojiPickerComponent)`
position: relative;
`;
+const Divider = styled.div`
+ width: 0.125rem;
+ height: 75%;
+ border-radius: 0.5rem;
+ background-color: ${colorGrayLightest};
+ align-self: center;
+`;
+
export {
Container,
EmojiPicker,
EmojiPickerWrapper,
EmojiButton,
+ Root,
+ Divider,
};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts
index d036de49e5..cc18419ac5 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts
@@ -1,22 +1,26 @@
import styled, { css } from 'styled-components';
import {
- borderSize,
userIndicatorsOffset,
smPaddingX,
+ smPaddingY,
+ lgPadding,
+ $3xlPadding,
+ xlPadding,
+ mdPadding,
} from '/imports/ui/stylesheets/styled-components/general';
import {
- lineHeightComputed,
fontSizeBase,
+ fontSizeSmaller,
} from '/imports/ui/stylesheets/styled-components/typography';
import {
colorWhite,
userListBg,
colorSuccess,
- colorOffWhite,
- colorText,
- palettePlaceholderText,
+ colorBlueLightest,
+ colorGrayLight,
+ colorGrayLightest,
} from '/imports/ui/stylesheets/styled-components/palette';
import Header from '/imports/ui/components/common/control-header/component';
@@ -26,17 +30,16 @@ interface ChatWrapperProps {
isSystemSender: boolean;
isPresentationUpload?: boolean;
isCustomPluginMessage: boolean;
- $highlight: boolean;
- $toolbarMenuIsOpen: boolean;
- $reactionPopoverIsOpen: boolean;
}
interface ChatContentProps {
sameSender: boolean;
isCustomPluginMessage: boolean;
+ $editing: boolean;
$highlight: boolean;
- $toolbarMenuIsOpen: boolean;
$reactionPopoverIsOpen: boolean;
+ $focused: boolean;
+ $keyboardFocused: boolean;
}
interface ChatAvatarProps {
@@ -48,12 +51,17 @@ interface ChatAvatarProps {
export const ChatWrapper = styled.div`
pointer-events: auto;
+ display: flex;
+ flex-flow: column;
+ gap: ${smPaddingY};
+ position: relative;
+ font-size: ${fontSizeBase};
+ position: relative;
+
[dir='rtl'] & {
direction: rtl;
}
- display: flex;
- flex-flow: row;
- position: relative;
+
${({ isPresentationUpload }) => isPresentationUpload && `
border-left: 2px solid #0F70D7;
margin-top: 1rem;
@@ -61,18 +69,6 @@ export const ChatWrapper = styled.div`
word-break: break-word;
background-color: #F3F6F9;
`}
- ${({ sameSender }) => sameSender && `
- flex: 1;
- margin: ${borderSize} 0 0 ${borderSize};
- margin-top: calc(${lineHeightComputed} / 3);
- `}
- ${({ sameSender }) => !sameSender && `
- padding-top:${lineHeightComputed};
- `}
- [dir="rtl"] & {
- margin: ${borderSize} ${borderSize} 0 0;
- }
- font-size: ${fontSizeBase};
${({ isSystemSender }) => isSystemSender && `
background-color: #fef9f1;
border-left: 2px solid #f5c67f;
@@ -83,42 +79,25 @@ export const ChatWrapper = styled.div`
margin: 0;
padding: 0;
`}
- ${({ sameSender, $highlight }) => !sameSender && $highlight && `
- &:hover {
- background-color: ${colorOffWhite};
- }
- border-radius: 6px;
- `}
- ${({ sameSender, $toolbarMenuIsOpen, $reactionPopoverIsOpen }) => !sameSender
- && ($toolbarMenuIsOpen || $reactionPopoverIsOpen)
- && `
- background-color: ${colorOffWhite};
- border-radius: 6px;
- `}
`;
export const ChatContent = styled.div`
display: flex;
flex-flow: column;
width: 100%;
+ border-radius: 0.5rem;
- ${({ sameSender, isCustomPluginMessage }) => sameSender
- && !isCustomPluginMessage && `
- margin-left: 2.6rem;
- `}
-
- ${({ sameSender, $highlight }) => sameSender && $highlight && `
- &:hover {
- background-color: ${colorOffWhite};
+ ${({ $highlight }) => $highlight && `
+ .chat-message-wrapper:hover > & {
+ background-color: ${colorBlueLightest};
}
- border-radius: 6px;
`}
- ${({ sameSender, $toolbarMenuIsOpen, $reactionPopoverIsOpen }) => sameSender
- && ($toolbarMenuIsOpen || $reactionPopoverIsOpen)
+ ${({
+ $editing, $reactionPopoverIsOpen, $focused, $keyboardFocused,
+ }) => ($reactionPopoverIsOpen || $editing || $focused || $keyboardFocused)
&& `
- background-color: ${colorOffWhite};
- border-radius: 6px;
+ background-color: ${colorBlueLightest};
`}
`;
@@ -212,29 +191,46 @@ export const ChatAvatar = styled.div`
}
`;
-export const Container = styled.div`
+export const Container = styled.div<{ $sequence: number }>`
display: flex;
flex-direction: column;
+
+ &:not(:first-child) {
+ margin-top: calc((${fontSizeSmaller} + ${lgPadding} * 2) / 2);
+ }
`;
-export const MessageItemWrapper = styled.div`
+export const MessageItemWrapper = styled.div<{ $edited: boolean, $sameSender: boolean }>`
display: flex;
flex-direction: row;
+ padding: ${lgPadding} ${$3xlPadding};
+
+ ${({ $edited, $sameSender }) => $edited && $sameSender && `
+ padding-bottom: 0;
+ `}
`;
export const DeleteMessage = styled.span`
- font-style: italic;
- font-weight: bold;
- color: ${colorText};
+ color: ${colorGrayLight};
+ padding: ${mdPadding} ${xlPadding};
+ border: 1px solid ${colorGrayLightest};
+ border-radius: 0.375rem;
`;
-export const ChatEditTime = styled.time`
- flex-shrink: 1;
- flex-grow: 0;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- min-width: 0;
- font-size: 75%;
- color: ${palettePlaceholderText};
+export const ChatHeading = styled.div`
+ display: flex;
+`;
+
+export const EditLabel = styled.span`
+ color: ${colorGrayLight};
+ font-size: 75%;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 2px;
+`;
+
+export const EditLabelWrapper = styled.div`
+ line-height: 1;
+ padding: ${xlPadding};
`;
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/component.tsx
index 6c1a9dbe11..0bfbb21e6e 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/component.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, {
useContext,
useEffect,
@@ -20,6 +19,7 @@ import { GraphqlDataHookSubscriptionResponse } from '/imports/ui/Types/hook';
import { useCreateUseSubscription } from '/imports/ui/core/hooks/createUseSubscription';
import { setLoadedMessageGathering } from '/imports/ui/core/hooks/useLoadedChatMessages';
import { ChatLoading } from '../../component';
+import { ChatEvents } from '/imports/ui/core/enums/chat';
interface ChatListPageContainerProps {
page: number;
@@ -29,6 +29,7 @@ interface ChatListPageContainerProps {
chatId: string;
markMessageAsSeen: (message: Message) => void;
scrollRef: React.RefObject;
+ focusedId: number | null;
}
interface ChatListPageProps {
@@ -38,6 +39,7 @@ interface ChatListPageProps {
page: number;
markMessageAsSeen: (message: Message)=> void;
scrollRef: React.RefObject;
+ focusedId: number | null;
}
const areChatPagesEqual = (prevProps: ChatListPageProps, nextProps: ChatListPageProps) => {
@@ -53,7 +55,7 @@ const areChatPagesEqual = (prevProps: ChatListPageProps, nextProps: ChatListPage
&& prevMessage?.message === nextMessage?.message
&& prevMessage?.reactions?.length === nextMessage?.reactions?.length
);
- });
+ }) && prevProps.focusedId === nextProps.focusedId;
};
const ChatListPage: React.FC = ({
@@ -63,6 +65,7 @@ const ChatListPage: React.FC = ({
page,
markMessageAsSeen,
scrollRef,
+ focusedId,
}) => {
const { domElementManipulationIdentifiers } = useContext(PluginsContext);
@@ -83,6 +86,29 @@ const ChatListPage: React.FC = ({
);
}, [domElementManipulationIdentifiers, messagesRequestedFromPlugin]);
+ const [keyboardFocusedMessageSequence, setKeyboardFocusedMessageSequence] = useState(null);
+ useEffect(() => {
+ const handleKeyboardFocusMessageRequest = (e: Event) => {
+ if (e instanceof CustomEvent) {
+ setKeyboardFocusedMessageSequence(Number.parseInt(e.detail.sequence, 10));
+ }
+ };
+
+ const handleKeyboardFocusMessageCancel = (e: Event) => {
+ if (e instanceof CustomEvent) {
+ setKeyboardFocusedMessageSequence(null);
+ }
+ };
+
+ window.addEventListener(ChatEvents.CHAT_KEYBOARD_FOCUS_MESSAGE_REQUEST, handleKeyboardFocusMessageRequest);
+ window.addEventListener(ChatEvents.CHAT_KEYBOARD_FOCUS_MESSAGE_CANCEL, handleKeyboardFocusMessageCancel);
+
+ return () => {
+ window.removeEventListener(ChatEvents.CHAT_KEYBOARD_FOCUS_MESSAGE_REQUEST, handleKeyboardFocusMessageRequest);
+ window.removeEventListener(ChatEvents.CHAT_KEYBOARD_FOCUS_MESSAGE_CANCEL, handleKeyboardFocusMessageCancel);
+ };
+ }, []);
+
return (
{messages.map((message, index, messagesArray) => {
@@ -99,6 +125,8 @@ const ChatListPage: React.FC = ({
scrollRef={scrollRef}
markMessageAsSeen={markMessageAsSeen}
messageReadFeedbackEnabled={messageReadFeedbackEnabled}
+ focused={focusedId === message.messageSequence}
+ keyboardFocused={keyboardFocusedMessageSequence === message.messageSequence}
/>
);
})}
@@ -116,8 +144,8 @@ const ChatListPageContainer: React.FC = ({
chatId,
markMessageAsSeen,
scrollRef,
+ focusedId,
}) => {
- // @ts-ignore - temporary, while meteor exists in the project
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
const PUBLIC_GROUP_CHAT_KEY = CHAT_CONFIG.public_group_id;
const PRIVATE_MESSAGE_READ_FEEDBACK_ENABLED = CHAT_CONFIG.privateMessageReadFeedback.enabled;
@@ -157,6 +185,7 @@ const ChatListPageContainer: React.FC = ({
page={page}
markMessageAsSeen={markMessageAsSeen}
scrollRef={scrollRef}
+ focusedId={focusedId}
/>
);
};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/styles.ts
index 23997f6f62..1f2c0c0619 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/styles.ts
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/styles.ts
@@ -1,6 +1,5 @@
import styled from 'styled-components';
import {
- mdPaddingX,
smPaddingX,
borderRadius,
} from '/imports/ui/stylesheets/styled-components/general';
@@ -15,8 +14,7 @@ interface MessageListProps {
export const MessageList = styled(ScrollboxVertical)`
flex-flow: column;
flex-shrink: 1;
- right: 0 ${mdPaddingX} 0 0;
- padding-top: 0;
+ padding-top: 2rem;
outline-style: none;
overflow-x: hidden;
user-select: text;
@@ -27,11 +25,6 @@ export const MessageList = styled(ScrollboxVertical)`
display: flex;
padding-bottom: ${smPaddingX};
- [dir='rtl'] & {
- margin: 0 0 0 auto;
- padding: 0 0 0 ${mdPaddingX};
- }
-
${({ isRTL }) => isRTL && `
padding-left: ${smPaddingX};
`}
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/component.tsx
index 5f0a4e444d..e85daff3f6 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/component.tsx
@@ -13,7 +13,7 @@ const ChatReplyIntention = () => {
const [sequence, setSequence] = useState();
useEffect(() => {
- const handler = (e: Event) => {
+ const handleReplyIntention = (e: Event) => {
if (e instanceof CustomEvent) {
setUsername(e.detail.username);
setMessage(e.detail.message);
@@ -22,10 +22,21 @@ const ChatReplyIntention = () => {
}
};
- window.addEventListener(ChatEvents.CHAT_REPLY_INTENTION, handler);
+ const handleCancelReplyIntention = (e: Event) => {
+ if (e instanceof CustomEvent) {
+ setUsername(undefined);
+ setMessage(undefined);
+ setEmphasizedMessage(undefined);
+ setSequence(undefined);
+ }
+ };
+
+ window.addEventListener(ChatEvents.CHAT_REPLY_INTENTION, handleReplyIntention);
+ window.addEventListener(ChatEvents.CHAT_CANCEL_REPLY_INTENTION, handleCancelReplyIntention);
return () => {
- window.removeEventListener(ChatEvents.CHAT_REPLY_INTENTION, handler);
+ window.removeEventListener(ChatEvents.CHAT_REPLY_INTENTION, handleReplyIntention);
+ window.removeEventListener(ChatEvents.CHAT_CANCEL_REPLY_INTENTION, handleCancelReplyIntention);
};
}, []);
@@ -33,9 +44,11 @@ const ChatReplyIntention = () => {
animations: boolean;
};
+ const hidden = !username || !message;
+
return (
{
window.dispatchEvent(
@@ -49,25 +62,23 @@ const ChatReplyIntention = () => {
if (sequence) Storage.setItem(ChatEvents.CHAT_FOCUS_MESSAGE_REQUEST, sequence);
}}
>
- {username}
{
- setMessage(undefined);
- setUsername(undefined);
+ onClick={(e) => {
+ e.stopPropagation();
+ window.dispatchEvent(
+ new CustomEvent(ChatEvents.CHAT_CANCEL_REPLY_INTENTION),
+ );
}}
icon="close"
- ghost
- circle
- color="light"
- size="sm"
+ tabIndex={hidden ? -1 : 0}
+ aria-hidden={hidden}
/>
);
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/styles.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/styles.tsx
index 2f377debae..dbf60f6bc5 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/styles.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-reply-intention/styles.tsx
@@ -1,68 +1,82 @@
import styled, { css } from 'styled-components';
import {
- colorBlueLight,
+ colorGrayLightest,
colorOffWhite,
+ colorPrimary,
} from '/imports/ui/stylesheets/styled-components/palette';
-import Button from '/imports/ui/components/common/button/component';
+import {
+ mdPadding, smPadding, smPaddingX, xlPadding,
+} from '/imports/ui/stylesheets/styled-components/general';
+import EmojiButton from '../chat-message-list/page/chat-message/message-toolbar/emoji-button/component';
const Container = styled.div<{ $hidden: boolean; $animations: boolean }>`
- border-radius: 4px;
- border-left: 4px solid ${colorBlueLight};
+ border-radius: 0.375rem;
background-color: ${colorOffWhite};
position: relative;
overflow: hidden;
+ box-shadow: inset 0 0 0 1px ${colorGrayLightest};
+ display: flex;
+
+ [dir='ltr'] & {
+ border-right: 0.375rem solid ${colorPrimary};
+ }
+
+ [dir='rtl'] & {
+ border-left: 0.375rem solid ${colorPrimary};
+ }
${({ $hidden }) => ($hidden
? css`
height: 0;
+ min-height: 0;
`
: css`
- height: 6rem;
- padding: 6px;
- margin-right: 0.75rem;
- margin-bottom: 0.25rem;
+ min-height: calc(1rem + ${mdPadding} * 2);
+ height: calc(1rem + ${mdPadding} * 2);
+ padding: ${mdPadding} calc(${smPaddingX} * 1.25);
+ margin-bottom: ${smPadding};
+
+ [dir='ltr'] & {
+ margin-right: ${xlPadding};
+ }
+
+ [dir='rtl'] & {
+ margin-left: ${xlPadding};
+ }
`
)}
${({ $animations }) => $animations
&& css`
- transition-property: height;
+ transition-property: height, min-height;
transition-duration: 0.1s;
`}
`;
const Typography = styled.div`
- line-height: 1rem;
+ line-height: 1;
font-size: 1rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`;
-const Username = styled(Typography)`
- font-weight: bold;
- color: ${colorBlueLight};
- line-height: 1rem;
- height: 1rem;
+const Message = styled(Typography)`
font-size: 1rem;
-`;
-
-const Message = styled.div`
- // Container height - Username height - vertical padding
- max-height: calc(5rem - 12px);
+ line-height: 1;
+ white-space: nowrap;
overflow: hidden;
+ flex-grow: 1;
`;
-// @ts-ignore
-const CloseBtn = styled(Button)`
- position: absolute;
- top: 2px;
- right: 2px;
+const CloseBtn = styled(EmojiButton)`
+ font-size: 75%;
+ height: 1rem;
+ padding: 0;
`;
export default {
Container,
- Username,
CloseBtn,
Message,
};
diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/component.tsx
index d659926a4b..5f22371ad7 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/component.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useRef } from 'react';
import { CircularProgress } from '@mui/material';
import ChatHeader from './chat-header/component';
import { layoutSelect, layoutSelectInput } from '../../layout/context';
@@ -14,6 +14,7 @@ import { Chat as ChatType } from '/imports/ui/Types/chat';
import { layoutDispatch } from '/imports/ui/components/layout/context';
import browserInfo from '/imports/utils/browserInfo';
import { GraphqlDataHookSubscriptionResponse } from '/imports/ui/Types/hook';
+import { ChatEvents } from '/imports/ui/core/enums/chat';
interface ChatProps {
isRTL: boolean;
@@ -21,6 +22,7 @@ interface ChatProps {
const Chat: React.FC = ({ isRTL }) => {
const { isChrome } = browserInfo;
+ const isEditingMessage = useRef(false);
React.useEffect(() => {
const handleMouseDown = () => {
@@ -32,10 +34,36 @@ const Chat: React.FC = ({ isRTL }) => {
}
};
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape' && isEditingMessage.current) {
+ window.dispatchEvent(
+ new CustomEvent(ChatEvents.CHAT_CANCEL_EDIT_REQUEST),
+ );
+ }
+ };
+
+ const handleEditingMessage = (e: Event) => {
+ if (e instanceof CustomEvent) {
+ isEditingMessage.current = true;
+ }
+ };
+
+ const handleCancelEditingMessage = (e: Event) => {
+ if (e instanceof CustomEvent) {
+ isEditingMessage.current = false;
+ }
+ };
+
document.addEventListener('mousedown', handleMouseDown);
+ document.addEventListener('keydown', handleKeyDown);
+ window.addEventListener(ChatEvents.CHAT_EDIT_REQUEST, handleEditingMessage);
+ window.addEventListener(ChatEvents.CHAT_CANCEL_EDIT_REQUEST, handleCancelEditingMessage);
return () => {
document.removeEventListener('mousedown', handleMouseDown);
+ document.removeEventListener('keydown', handleKeyDown);
+ window.removeEventListener(ChatEvents.CHAT_EDIT_REQUEST, handleEditingMessage);
+ window.removeEventListener(ChatEvents.CHAT_CANCEL_EDIT_REQUEST, handleCancelEditingMessage);
};
}, []);
diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/external-video-player-graphql/component.tsx b/bigbluebutton-html5/imports/ui/components/external-video-player/external-video-player-graphql/component.tsx
index 2aaea07b8d..d6c7585482 100644
--- a/bigbluebutton-html5/imports/ui/components/external-video-player/external-video-player-graphql/component.tsx
+++ b/bigbluebutton-html5/imports/ui/components/external-video-player/external-video-player-graphql/component.tsx
@@ -73,9 +73,9 @@ interface ExternalVideoPlayerProps {
externalVideo: ExternalVideo;
playing: boolean;
playerPlaybackRate: number;
- key: string;
+ playerKey: string;
isSidebarContentOpen: boolean;
- setKey: (key: string) => void;
+ setPlayerKey: (key: string) => void;
sendMessage: (event: string, data: {
rate: number;
time: number;
@@ -104,8 +104,8 @@ const ExternalVideoPlayer: React.FC = ({
playing,
playerPlaybackRate,
isEchoTest,
- key,
- setKey,
+ playerKey,
+ setPlayerKey,
sendMessage,
getCurrentTime,
}) => {
@@ -130,15 +130,15 @@ const ExternalVideoPlayer: React.FC = ({
return {
// default option for all players, can be overwritten
playerOptions: {
- autoplay: true,
- playsinline: true,
+ autoPlay: true,
+ playsInline: true,
controls: isPresenter,
},
file: {
attributes: {
controls: isPresenter ? 'controls' : '',
- autoplay: 'autoplay',
- playsinline: 'playsinline',
+ autoPlay: true,
+ playsInline: true,
},
},
facebook: {
@@ -263,16 +263,14 @@ const ExternalVideoPlayer: React.FC = ({
useEffect(() => {
if (isPresenter !== presenterRef.current) {
- setKey(uniqueId('react-player'));
+ setPlayerKey(uniqueId('react-player'));
presenterRef.current = isPresenter;
}
}, [isPresenter]);
const handleOnStart = () => {
- if (!isPresenter) {
- currentTime = getCurrentTime();
- playerRef.current?.seekTo(truncateTime(currentTime), 'seconds');
- }
+ currentTime = getCurrentTime();
+ playerRef.current?.seekTo(truncateTime(currentTime), 'seconds');
};
const handleOnPlay = () => {
@@ -308,10 +306,6 @@ const ExternalVideoPlayer: React.FC = ({
}
};
- const handleOnReady = (reactPlayer: ReactPlayer) => {
- reactPlayer.seekTo(truncateTime(currentTime), 'seconds');
- };
-
const handleProgress = (state: OnProgressProps) => {
setPlayed(state.played);
setLoaded(state.loaded);
@@ -379,12 +373,11 @@ const ExternalVideoPlayer: React.FC = ({
url={videoUrl}
playing={playing}
playbackRate={playerPlaybackRate}
- key={key}
+ key={playerKey}
height="100%"
width="100%"
ref={playerRef}
volume={volume}
- onReady={handleOnReady}
onStart={handleOnStart}
onPlay={handleOnPlay}
onDuration={handleDuration}
@@ -397,7 +390,7 @@ const ExternalVideoPlayer: React.FC = ({
shouldShowTools() ? (
{ setMute(m); }}
- handleReload={() => setKey(uniqueId('react-player'))}
+ handleReload={() => setPlayerKey(uniqueId('react-player'))}
setShowHoverToolBar={setShowHoverToolBar}
toolbarStyle={toolbarStyle}
handleVolumeChanged={changeVolume}
@@ -578,8 +571,8 @@ const ExternalVideoPlayerContainer: React.FC = () => {
fullscreenContext={fullscreenContext}
externalVideo={externalVideo}
getCurrentTime={getCurrentTime}
- key={key}
- setKey={setKey}
+ playerKey={key}
+ setPlayerKey={setKey}
sendMessage={sendMessage}
/>
);
diff --git a/bigbluebutton-html5/imports/ui/core/enums/chat.ts b/bigbluebutton-html5/imports/ui/core/enums/chat.ts
index 07bbcaa7bc..c5548e57c3 100644
--- a/bigbluebutton-html5/imports/ui/core/enums/chat.ts
+++ b/bigbluebutton-html5/imports/ui/core/enums/chat.ts
@@ -1,8 +1,12 @@
export const enum ChatEvents {
SENT_MESSAGE = 'sentMessage',
CHAT_FOCUS_MESSAGE_REQUEST = 'ChatFocusMessageRequest',
+ CHAT_KEYBOARD_FOCUS_MESSAGE_REQUEST = 'ChatKeyboardFocusMessageRequest',
+ CHAT_KEYBOARD_FOCUS_MESSAGE_CANCEL = 'ChatKeyboardFocusMessageCancel',
CHAT_REPLY_INTENTION = 'ChatReplyIntention',
+ CHAT_CANCEL_REPLY_INTENTION = 'ChatCancelReplyIntention',
CHAT_EDIT_REQUEST = 'ChatEditRequest',
+ CHAT_CANCEL_EDIT_REQUEST = 'ChatCancelEditRequest',
CHAT_DELETE_REQUEST = 'ChatDeleteRequest',
}
diff --git a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/general.js b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/general.js
index 8b45d81728..98f99c1f7c 100644
--- a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/general.js
+++ b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/general.js
@@ -11,6 +11,14 @@ const lgPaddingY = '0.6rem';
const jumboPaddingY = '1.5rem';
const jumboPaddingX = '3.025rem';
+const xsPadding = '0.125rem';
+const smPadding = '0.25rem';
+const mdPadding = '0.375rem';
+const lgPadding = '0.5rem';
+const xlPadding = '0.75rem';
+const $2xlPadding = '1rem';
+const $3xlPadding = '1.25rem';
+
const whiteboardToolbarPadding = '.5rem';
const whiteboardToolbarMargin = '.5rem';
const whiteboardToolbarPaddingSm = '.3rem';
@@ -170,4 +178,11 @@ export {
presentationMenuHeight,
styleMenuOffset,
styleMenuOffsetSmall,
+ lgPadding,
+ mdPadding,
+ smPadding,
+ $2xlPadding,
+ $3xlPadding,
+ xlPadding,
+ xsPadding,
};
diff --git a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js
index 91c26ee77d..2573a15d2b 100644
--- a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js
+++ b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js
@@ -12,6 +12,7 @@ const colorGrayLightest = 'var(--color-gray-lightest, #D4D9DF)';
const colorBlueLight = 'var(--color-blue-light, #54a1f3)';
const colorBlueLighter = 'var(--color-blue-lighter, #92BCEA)';
const colorBlueLightest = 'var(--color-blue-lightest, #E4ECF2)';
+const colorBlueLightestChannel = '228 236 242';
const colorTransparent = 'var(--color-transparent, #ff000000)';
@@ -135,6 +136,7 @@ export {
colorBlueLight,
colorBlueLighter,
colorBlueLightest,
+ colorBlueLightestChannel,
colorPrimary,
colorDanger,
colorDangerDark,
diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json
index d300e7e311..2b5f7dc4ff 100644
--- a/bigbluebutton-html5/package-lock.json
+++ b/bigbluebutton-html5/package-lock.json
@@ -20,6 +20,7 @@
"@emotion/styled": "^11.10.8",
"@jitsi/sdp-interop": "0.1.14",
"@mconf/bbb-diff": "^1.2.0",
+ "@mui/base": "^5.0.0-beta.58",
"@mui/material": "^5.12.2",
"@mui/system": "^5.12.3",
"@types/node": "^20.5.7",
@@ -2796,6 +2797,68 @@
"diff": "^5.0.0"
}
},
+ "node_modules/@mui/base": {
+ "version": "5.0.0-beta.58",
+ "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.58.tgz",
+ "integrity": "sha512-P0E7ZrxOuyYqBvVv9w8k7wm+Xzx/KRu+BGgFcR2htTsGCpJNQJCSUXNUZ50MUmSU9hzqhwbQWNXhV1MBTl6F7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.0",
+ "@floating-ui/react-dom": "^2.1.1",
+ "@mui/types": "^7.2.15",
+ "@mui/utils": "6.0.0-rc.0",
+ "@popperjs/core": "^2.11.8",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0",
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/base/node_modules/@mui/utils": {
+ "version": "6.0.0-rc.0",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.0.0-rc.0.tgz",
+ "integrity": "sha512-tBp0ILEXDL0bbDDT8PnZOjCqSm5Dfk2N0Z45uzRw+wVl6fVvloC9zw8avl+OdX1Bg3ubs/ttKn8nRNv17bpM5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.0",
+ "@mui/types": "^7.2.15",
+ "@types/prop-types": "^15.7.12",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@mui/core-downloads-tracker": {
"version": "5.16.6",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.6.tgz",
diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json
index c067df2c9d..8353b0d68c 100644
--- a/bigbluebutton-html5/package.json
+++ b/bigbluebutton-html5/package.json
@@ -49,6 +49,7 @@
"@emotion/styled": "^11.10.8",
"@jitsi/sdp-interop": "0.1.14",
"@mconf/bbb-diff": "^1.2.0",
+ "@mui/base": "^5.0.0-beta.58",
"@mui/material": "^5.12.2",
"@mui/system": "^5.12.3",
"@types/node": "^20.5.7",
diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json
index 7db1d69a43..92b94e4bf0 100755
--- a/bigbluebutton-html5/public/locales/en.json
+++ b/bigbluebutton-html5/public/locales/en.json
@@ -29,8 +29,8 @@
"app.chat.breakoutDurationUpdated": "Breakout time is now {0} minutes",
"app.chat.breakoutDurationUpdatedModerator": "Breakout rooms time is now {0} minutes, and a notification has been sent.",
"app.chat.emptyLogLabel": "Chat log empty",
- "app.chat.away": "is away",
- "app.chat.notAway": "is back",
+ "app.chat.away": "{user} is away",
+ "app.chat.notAway": "{user} is back online",
"app.chat.clearPublicChatMessage": "The public chat history was cleared by a moderator",
"app.chat.multi.typing": "Multiple users are typing",
"app.chat.someone.typing": "Someone is typing",
@@ -49,6 +49,12 @@
"app.chat.toolbar.reactions.youLabel": "you",
"app.chat.toolbar.reactions.andLabel": "and",
"app.chat.toolbar.reactions.findReactionButtonLabel": "Find a reaction",
+ "app.chat.toolbar.edit.editing": "Editing message",
+ "app.chat.toolbar.edit.cancel": "Press {key} to cancel.",
+ "app.chat.toolbar.edit.edited": "Edited",
+ "app.chat.toolbar.delete.cancelLabel": "Cancel",
+ "app.chat.toolbar.delete.confirmationTitle": "Are you sure?",
+ "app.chat.toolbar.delete.confirmationDescription": "This action is permanent, you will not be able to access this message again.",
"app.timer.toolTipTimerStopped": "The timer has stopped.",
"app.timer.toolTipTimerRunning": "The timer is running.",
"app.timer.toolTipStopwatchStopped": "The stopwatch has stopped.",