Add user chat message badges. Closes #1988

This commit is contained in:
Gabe Kangas
2022-08-21 14:04:16 -07:00
parent eac7e81a9c
commit 3cfcad7a93
6 changed files with 70 additions and 2 deletions

View File

@@ -88,6 +88,7 @@ export default function ChatContainer(props: Props) {
sentBySelf={message.user?.id === chatUserId} // The local user sent this message sentBySelf={message.user?.id === chatUserId} // The local user sent this message
sameUserAsLast={shouldCollapseMessages(messages, index)} sameUserAsLast={shouldCollapseMessages(messages, index)}
isAuthorModerator={(message as ChatMessage).user.scopes?.includes('MODERATOR')} isAuthorModerator={(message as ChatMessage).user.scopes?.includes('MODERATOR')}
isAuthorAuthenticated={message.user?.authenticated}
key={message.id} key={message.id}
/> />
); );

View File

@@ -0,0 +1,13 @@
@import 'styles/mixins.scss';
.badge {
font-family: var(--theme-header-font-family);
font-weight: 500;
font-size: 0.4em;
text-transform: uppercase;
padding: 2px;
border-radius: 3px;
border-width: 1px;
border-style: solid;
margin-left: 3px;
}

View File

@@ -0,0 +1,18 @@
import s from './ChatUserBadge.module.scss';
interface Props {
badge: string;
userColor: number;
}
export default function ChatUserBadge(props: Props) {
const { badge, userColor } = props;
const color = `var(--theme-user-colors-${userColor})`;
const style = { color, borderColor: color };
return (
<span style={style} className={s.badge}>
{badge}
</span>
);
}

View File

@@ -6,8 +6,8 @@ import cn from 'classnames';
import s from './ChatUserMessage.module.scss'; import s from './ChatUserMessage.module.scss';
import { formatTimestamp } from './messageFmt'; import { formatTimestamp } from './messageFmt';
import { ChatMessage } from '../../../interfaces/chat-message.model'; import { ChatMessage } from '../../../interfaces/chat-message.model';
import { ModIcon } from '../../ui';
import ChatModerationActionMenu from '../ChatModerationActionMenu/ChatModerationActionMenu'; import ChatModerationActionMenu from '../ChatModerationActionMenu/ChatModerationActionMenu';
import ChatUserBadge from './ChatUserBadge';
interface Props { interface Props {
message: ChatMessage; message: ChatMessage;
@@ -16,6 +16,7 @@ interface Props {
sentBySelf: boolean; sentBySelf: boolean;
sameUserAsLast: boolean; sameUserAsLast: boolean;
isAuthorModerator: boolean; isAuthorModerator: boolean;
isAuthorAuthenticated: boolean;
} }
export default function ChatUserMessage({ export default function ChatUserMessage({
@@ -25,6 +26,7 @@ export default function ChatUserMessage({
sentBySelf, // Move the border to the right and render a background sentBySelf, // Move the border to the right and render a background
sameUserAsLast, sameUserAsLast,
isAuthorModerator, isAuthorModerator,
isAuthorAuthenticated,
}: Props) { }: Props) {
const { id: messageId, body, user, timestamp } = message; const { id: messageId, body, user, timestamp } = message;
const { id: userId, displayName, displayColor } = user; const { id: userId, displayName, displayColor } = user;
@@ -33,6 +35,11 @@ export default function ChatUserMessage({
const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`; const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`;
const [formattedMessage, setFormattedMessage] = useState<string>(body); const [formattedMessage, setFormattedMessage] = useState<string>(body);
const badgeStrings = [isAuthorModerator && 'mod', isAuthorAuthenticated && 'auth'];
const badges = badgeStrings
.filter(badge => !!badge)
.map(badge => <ChatUserBadge key={badge} badge={badge} userColor={displayColor} />);
useEffect(() => { useEffect(() => {
setFormattedMessage(he.decode(body)); setFormattedMessage(he.decode(body));
}, [message]); }, [message]);
@@ -49,7 +56,7 @@ export default function ChatUserMessage({
{!sameUserAsLast && ( {!sameUserAsLast && (
<div className={s.user} style={{ color }}> <div className={s.user} style={{ color }}>
<span className={s.userName}>{displayName}</span> <span className={s.userName}>{displayName}</span>
{isAuthorModerator && <ModIcon />} <span>{badges}</span>
</div> </div>
)} )}
<Highlight search={highlightString}> <Highlight search={highlightString}>

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import ChatUserBadge from '../components/chat/ChatUserMessage/ChatUserBadge';
export default {
title: 'owncast/Chat/Messages/User Flag',
component: ChatUserBadge,
argTypes: {
userColor: {
options: ['0', '1', '2', '3', '4', '5', '6', '7'],
control: { type: 'select' },
},
},
} as ComponentMeta<typeof ChatUserBadge>;
const Template: ComponentStory<typeof ChatUserBadge> = args => <ChatUserBadge {...args} />;
export const Moderator = Template.bind({});
Moderator.args = {
badge: 'mod',
userColor: '5',
};
export const Authenticated = Template.bind({});
Authenticated.args = {
badge: 'auth',
userColor: '6',
};

View File

@@ -92,6 +92,7 @@ export const FromAuthenticatedUser = Template.bind({});
FromAuthenticatedUser.args = { FromAuthenticatedUser.args = {
message: authenticatedUserMessage, message: authenticatedUserMessage,
showModeratorMenu: false, showModeratorMenu: false,
isAuthorAuthenticated: true,
}; };
export const WithStringHighlighted = Template.bind({}); export const WithStringHighlighted = Template.bind({});