Add user chat message badges. Closes #1988
This commit is contained in:
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
18
web/components/chat/ChatUserMessage/ChatUserBadge.tsx
Normal file
18
web/components/chat/ChatUserMessage/ChatUserBadge.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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}>
|
||||||
|
|||||||
28
web/stories/ChatUserBadge.stories.tsx
Normal file
28
web/stories/ChatUserBadge.stories.tsx
Normal 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',
|
||||||
|
};
|
||||||
@@ -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({});
|
||||||
|
|||||||
Reference in New Issue
Block a user