{isMentionEnabled && channel.unreadMentionCount > 0 ? (
+
+
diff --git a/src/svgs/icon-mark-as-unread.svg b/src/svgs/icon-mark-as-unread.svg
new file mode 100644
index 000000000..2eacb63f0
--- /dev/null
+++ b/src/svgs/icon-mark-as-unread.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/ui/DateSeparator/index.tsx b/src/ui/DateSeparator/index.tsx
index 837976273..65df3a29c 100644
--- a/src/ui/DateSeparator/index.tsx
+++ b/src/ui/DateSeparator/index.tsx
@@ -14,11 +14,13 @@ export interface DateSeparatorProps {
className?: string | Array;
separatorColor?: Colors;
}
+
const DateSeparator = ({
children = undefined,
className = '',
separatorColor = Colors.ONBACKGROUND_4,
}: DateSeparatorProps): ReactElement => {
+
return (
;
case Types.FEEDBACK_LIKE: return ;
case Types.FEEDBACK_DISLIKE: return ;
+ case Types.MARK_AS_UNREAD: return ;
+ case Types.FLOATING_BUTTON_CLOSE: return ;
default: return 'icon'; // If you see this text 'icon' replace icon for it
}
}
diff --git a/src/ui/Icon/type.ts b/src/ui/Icon/type.ts
index 5030719e5..b9622a596 100644
--- a/src/ui/Icon/type.ts
+++ b/src/ui/Icon/type.ts
@@ -32,6 +32,7 @@ export const Types = {
GIF: 'GIF',
INFO: 'INFO',
LEAVE: 'LEAVE',
+ MARK_AS_UNREAD: 'MARK_AS_UNREAD',
MEMBERS: 'MEMBERS',
MESSAGE: 'MESSAGE',
MODERATIONS: 'MODERATIONS',
@@ -60,5 +61,6 @@ export const Types = {
USER: 'USER',
FEEDBACK_LIKE: 'FEEDBACK_LIKE',
FEEDBACK_DISLIKE: 'FEEDBACK_DISLIKE',
+ FLOATING_BUTTON_CLOSE: 'FLOATING_BUTTON_CLOSE',
} as const;
export type Types = typeof Types[keyof typeof Types];
diff --git a/src/ui/Icon/utils.ts b/src/ui/Icon/utils.ts
index d1451d1f8..446320be9 100644
--- a/src/ui/Icon/utils.ts
+++ b/src/ui/Icon/utils.ts
@@ -59,6 +59,7 @@ export function changeTypeToIconClassName(type: Types): string {
case Types.GIF: return 'sendbird-icon-gif';
case Types.INFO: return 'sendbird-icon-info';
case Types.LEAVE: return 'sendbird-icon-leave';
+ case Types.MARK_AS_UNREAD: return 'sendbird-icon-mark-as-unread';
case Types.MEMBERS: return 'sendbird-icon-members';
case Types.MESSAGE: return 'sendbird-icon-message';
case Types.MODERATIONS: return 'sendbird-icon-moderations';
diff --git a/src/ui/Label/stringSet.ts b/src/ui/Label/stringSet.ts
index f099849aa..7df588e90 100644
--- a/src/ui/Label/stringSet.ts
+++ b/src/ui/Label/stringSet.ts
@@ -12,7 +12,10 @@ const stringSet = {
en: {
// Group Channel - Conversation
MESSAGE_STATUS__YESTERDAY: 'Yesterday',
- CHANNEL__MESSAGE_LIST__NOTIFICATION__NEW_MESSAGE: 'new message(s) since',
+ CHANNEL__MESSAGE_LIST__NOTIFICATION__NEW_MESSAGE: 'new message',
+ CHANNEL__MESSAGE_LIST__NOTIFICATION__NEW_MESSAGE_S: 'new messages',
+ CHANNEL__MESSAGE_LIST__NOTIFICATION__UNREAD_MESSAGE: 'unread message',
+ CHANNEL__MESSAGE_LIST__NOTIFICATION__UNREAD_MESSAGE_S: 'unread messages',
/** @deprecated Please use `DATE_FORMAT__MESSAGE_LIST__NOTIFICATION__UNREAD_SINCE` instead * */
CHANNEL__MESSAGE_LIST__NOTIFICATION__ON: 'on',
// Channel List
@@ -91,7 +94,7 @@ const stringSet = {
TYPING_INDICATOR__AND: 'and',
TYPING_INDICATOR__ARE_TYPING: 'are typing...',
TYPING_INDICATOR__MULTIPLE_TYPING: 'Several people are typing...',
- CHANNEL_FROZEN: 'Channel frozen',
+ CHANNEL_FROZEN: 'Channel is frozen',
PLACE_HOLDER__NO_CHANNEL: 'No channels',
PLACE_HOLDER__WRONG: 'Something went wrong',
PLACE_HOLDER__RETRY_TO_CONNECT: 'Retry',
@@ -168,6 +171,7 @@ const stringSet = {
MESSAGE_MENU__RESEND: 'Resend',
MESSAGE_MENU__DELETE: 'Delete',
MESSAGE_MENU__SAVE: 'Save',
+ MESSAGE_MENU__MARK_AS_UNREAD: 'Mark as unread',
// * FIXME: get back legacy, remove after refactoring open channel messages *
CONTEXT_MENU_DROPDOWN__COPY: 'Copy',
CONTEXT_MENU_DROPDOWN__EDIT: 'Edit',
diff --git a/src/ui/MessageContent/index.tsx b/src/ui/MessageContent/index.tsx
index 58be3f11f..f33359210 100644
--- a/src/ui/MessageContent/index.tsx
+++ b/src/ui/MessageContent/index.tsx
@@ -77,6 +77,7 @@ export interface MessageContentProps extends MessageComponentRenderers {
deleteMessage?: (message: CoreMessageType) => Promise;
toggleReaction?: (message: SendableMessageType, reactionKey: string, isReacted: boolean) => void;
setQuoteMessage?: (message: SendableMessageType) => void;
+ markAsUnread?: (message: SendableMessageType) => void;
// onClick listener for thread replies view (for open thread module)
onReplyInThread?: (props: { message: SendableMessageType }) => void;
// onClick listener for thread quote message view (for open thread module)
@@ -120,6 +121,7 @@ export function MessageContent(props: MessageContentProps): ReactElement {
deleteMessage,
toggleReaction,
setQuoteMessage,
+ markAsUnread,
onReplyInThread,
onQuoteMessageClick,
onMessageHeightChange,
@@ -310,6 +312,7 @@ export function MessageContent(props: MessageContentProps): ReactElement {
showRemove,
resendMessage,
setQuoteMessage,
+ markAsUnread,
onReplyInThread: ({ message }) => {
if (threadReplySelectType === ThreadReplySelectType.THREAD) {
onReplyInThread?.({ message });
@@ -543,6 +546,7 @@ export function MessageContent(props: MessageContentProps): ReactElement {
showRemove,
resendMessage,
setQuoteMessage,
+ markAsUnread,
onReplyInThread: ({ message }) => {
if (threadReplySelectType === ThreadReplySelectType.THREAD) {
onReplyInThread?.({ message });
@@ -574,6 +578,7 @@ export function MessageContent(props: MessageContentProps): ReactElement {
setQuoteMessage,
toggleReaction,
showEdit,
+ markAsUnread,
onReplyInThread: ({ message }) => {
if (threadReplySelectType === ThreadReplySelectType.THREAD) {
onReplyInThread?.({ message });
diff --git a/src/ui/MessageMenu/MessageMenu.tsx b/src/ui/MessageMenu/MessageMenu.tsx
index ead309151..4057d33cb 100644
--- a/src/ui/MessageMenu/MessageMenu.tsx
+++ b/src/ui/MessageMenu/MessageMenu.tsx
@@ -16,6 +16,7 @@ import {
EditMenuItem,
ResendMenuItem,
DeleteMenuItem,
+ MarkAsUnreadMenuItem,
} from './menuItems/MessageMenuItems';
import { ReplyType } from '../../types';
import {
@@ -23,6 +24,7 @@ import {
showMenuItemCopy,
showMenuItemDelete,
showMenuItemEdit,
+ showMenuItemMarkAsUnread,
showMenuItemOpenInChannel,
showMenuItemReply,
showMenuItemResend,
@@ -41,6 +43,7 @@ export type RenderMenuItemsParams = {
EditMenuItem: (props: PrebuildMenuItemPropsType) => ReactElement;
ResendMenuItem: (props: PrebuildMenuItemPropsType) => ReactElement;
DeleteMenuItem: (props: PrebuildMenuItemPropsType) => ReactElement;
+ MarkAsUnreadMenuItem: (props: PrebuildMenuItemPropsType) => ReactElement;
};
};
export interface MessageMenuProps {
@@ -56,6 +59,7 @@ export interface MessageMenuProps {
showRemove?: (bool: boolean) => void;
deleteMessage?: (message: SendableMessageType) => void;
resendMessage?: (message: SendableMessageType) => void;
+ markAsUnread?: (message: SendableMessageType) => void;
setQuoteMessage?: (message: SendableMessageType) => void;
onReplyInThread?: (props: { message: SendableMessageType }) => void;
onMoveToParentMessage?: () => void;
@@ -75,11 +79,12 @@ export const MessageMenu = ({
showRemove = noop,
deleteMessage,
resendMessage,
+ markAsUnread,
setQuoteMessage,
onReplyInThread,
onMoveToParentMessage,
}: MessageMenuProps) => {
- const { state: { config: { isOnline } } } = useSendbird();
+ const { state: { config: { isOnline, groupChannel: { enableMarkAsUnread } } } } = useSendbird();
const triggerRef = useRef(null);
const containerRef = useRef(null);
@@ -114,6 +119,7 @@ export const MessageMenu = ({
showRemove,
deleteMessage,
resendMessage,
+ markAsUnread,
isOnline,
disableDeleteMessage,
triggerRef,
@@ -135,6 +141,7 @@ export const MessageMenu = ({
ThreadMenuItem,
OpenInChannelMenuItem,
EditMenuItem,
+ MarkAsUnreadMenuItem,
ResendMenuItem,
DeleteMenuItem,
},
@@ -145,6 +152,7 @@ export const MessageMenu = ({
{showMenuItemThread(params) && }
{showMenuItemOpenInChannel(params) && }
{showMenuItemEdit(params) && }
+ {enableMarkAsUnread && showMenuItemMarkAsUnread(params) && }
{showMenuItemResend(params) && }
{showMenuItemDelete(params) && }
>
diff --git a/src/ui/MessageMenu/MessageMenuProvider.tsx b/src/ui/MessageMenu/MessageMenuProvider.tsx
index 8dc80c95d..77c593837 100644
--- a/src/ui/MessageMenu/MessageMenuProvider.tsx
+++ b/src/ui/MessageMenu/MessageMenuProvider.tsx
@@ -11,6 +11,7 @@ interface CommonMessageMenuContextProps {
showRemove: (bool: boolean) => void;
deleteMessage: (message: SendableMessageType) => void;
resendMessage: (message: SendableMessageType) => void;
+ markAsUnread?: (message: SendableMessageType, source?: 'manual' | 'internal') => void;
isOnline: boolean;
disableDeleteMessage: boolean | null;
triggerRef: MutableRefObject;
diff --git a/src/ui/MessageMenu/menuItems/BottomSheetMenuItems.tsx b/src/ui/MessageMenu/menuItems/BottomSheetMenuItems.tsx
index 81d934146..f37b9623b 100644
--- a/src/ui/MessageMenu/menuItems/BottomSheetMenuItems.tsx
+++ b/src/ui/MessageMenu/menuItems/BottomSheetMenuItems.tsx
@@ -208,3 +208,30 @@ export const DownloadMenuItem = (props: PrebuildMenuItemPropsType) => {
);
};
+
+export const MarkAsUnreadMenuItem = (props: PrebuildMenuItemPropsType) => {
+ const { stringSet } = useLocalization();
+ const { message, hideMenu, markAsUnread } = useMessageMenuContext();
+
+ return (
+ {
+ if (markAsUnread) {
+ markAsUnread(message, 'manual');
+ }
+ hideMenu();
+ props.onClick?.(e);
+ }}
+ >
+ {props.children ?? (
+ <>
+
+
+ >
+ )}
+
+ );
+};
diff --git a/src/ui/MessageMenu/menuItems/MessageMenuItems.tsx b/src/ui/MessageMenu/menuItems/MessageMenuItems.tsx
index 2b5867014..c1c2f3fed 100644
--- a/src/ui/MessageMenu/menuItems/MessageMenuItems.tsx
+++ b/src/ui/MessageMenu/menuItems/MessageMenuItems.tsx
@@ -179,3 +179,23 @@ export const DeleteMenuItem = (props: PrebuildMenuItemPropsType) => {
);
};
+
+export const MarkAsUnreadMenuItem = (props: PrebuildMenuItemPropsType) => {
+ const { stringSet } = useLocalization();
+ const { message, hideMenu, markAsUnread } = useMessageMenuContext();
+
+ return (
+
+ );
+};
diff --git a/src/ui/MessageMenu/menuItems/MobileMenuItems.tsx b/src/ui/MessageMenu/menuItems/MobileMenuItems.tsx
index af89a8d12..8e6cc8b75 100644
--- a/src/ui/MessageMenu/menuItems/MobileMenuItems.tsx
+++ b/src/ui/MessageMenu/menuItems/MobileMenuItems.tsx
@@ -207,3 +207,30 @@ export const DownloadMenuItem = (props: PrebuildMenuItemPropsType) => {
);
};
+
+export const MarkAsUnreadMenuItem = (props: PrebuildMenuItemPropsType) => {
+ const { stringSet } = useLocalization();
+ const { message, hideMenu, markAsUnread } = useMessageMenuContext();
+
+ return (
+
+ );
+};
diff --git a/src/ui/MobileMenu/MobileBottomSheet.tsx b/src/ui/MobileMenu/MobileBottomSheet.tsx
index c78959d2a..f3a0efe97 100644
--- a/src/ui/MobileMenu/MobileBottomSheet.tsx
+++ b/src/ui/MobileMenu/MobileBottomSheet.tsx
@@ -29,6 +29,7 @@ import {
ThreadMenuItem,
DeleteMenuItem,
DownloadMenuItem,
+ MarkAsUnreadMenuItem,
} from '../MessageMenu/menuItems/BottomSheetMenuItems';
import useSendbird from '../../lib/Sendbird/context/hooks/useSendbird';
@@ -56,7 +57,7 @@ const MobileBottomSheet: React.FunctionComponent = (prop
renderMenuItems,
} = props;
const isByMe = message?.sender?.userId === userId;
- const { state: { config: { isOnline } } } = useSendbird();
+ const { state: { config: { isOnline, groupChannel: { enableMarkAsUnread } } } } = useSendbird();
const showMenuItemCopy: boolean = isUserMessage(message as UserMessage);
const showMenuItemEdit: boolean = (isUserMessage(message as UserMessage) && isSentMessage(message) && isByMe);
const showMenuItemResend: boolean = (isOnline && isFailedMessage(message) && message?.isResendable && isByMe);
@@ -82,6 +83,11 @@ const MobileBottomSheet: React.FunctionComponent = (prop
&& !isThreadMessage(message)
&& (channel?.isGroupChannel() && !(channel as GroupChannel)?.isBroadcast);
+ const showMenuItemMarkAsUnread: boolean = !isFailedMessage(message)
+ && !isPendingMessage(message)
+ && channel?.isGroupChannel?.()
+ && replyType !== 'THREAD';
+
const maxEmojisPerRow = Math.floor(window.innerWidth / EMOJI_SIZE) - 1;
const [showEmojisOnly, setShowEmojisOnly] = useState(false);
const emojis = emojiContainer && getEmojiListAll(emojiContainer);
@@ -98,6 +104,7 @@ const MobileBottomSheet: React.FunctionComponent = (prop
showRemove,
deleteMessage,
resendMessage,
+ markAsUnread: props.markAsUnread,
isOnline,
disableDeleteMessage: disableDelete,
triggerRef: null,
@@ -192,18 +199,20 @@ const MobileBottomSheet: React.FunctionComponent = (prop
ReplyMenuItem,
ThreadMenuItem,
DeleteMenuItem,
+ MarkAsUnreadMenuItem,
},
}) ?? (
<>
{showMenuItemCopy && }
{showMenuItemEdit && }
+ {enableMarkAsUnread && showMenuItemMarkAsUnread && }
{showMenuItemResend && }
{showMenuItemReply && }
{showMenuItemThread && }
{showMenuItemDeleteFinal && }
{showMenuItemDownload && }
>
- )}
+ )}ß
)}
diff --git a/src/ui/MobileMenu/MobileContextMenu.tsx b/src/ui/MobileMenu/MobileContextMenu.tsx
index 97a116664..e2798fa22 100644
--- a/src/ui/MobileMenu/MobileContextMenu.tsx
+++ b/src/ui/MobileMenu/MobileContextMenu.tsx
@@ -23,6 +23,7 @@ import {
ResendMenuItem,
DeleteMenuItem,
DownloadMenuItem,
+ MarkAsUnreadMenuItem,
} from '../MessageMenu/menuItems/MobileMenuItems';
import { MenuItems } from '../ContextMenu';
import { noop } from '../../utils/utils';
@@ -48,7 +49,7 @@ const MobileContextMenu: React.FunctionComponent