reafctor: normalize component formatting (#2082)
* refactor: move/rename BanUserButton file * refactor: move/rename Chart file * refactor: update generic component filenames to PascalCase * refactor: update config component filenames to PascalCase * refactor: update AdminLayout component filename to PascalCase * refactor: update/move VideoJS component * chore(eslint): disable bad react/require-default-props rule * refactor: normalize ActionButton component * refactor: normalize ActionButtonRow component * refactor: normalize FollowButton component * refactor: normalize NotifyButton component * refactor: normalize ChatActionMessage component * refactor: normalize ChatContainer component * refactor: normalize ChatJoinMessage component * refactor: normalize ChatModerationActionMenu component * refactor: normalize ChatModerationDetailsModal component * refactor: normalize ChatModeratorNotification component * refactor: normalize ChatSocialMessage component * refactor: normalize ChatSystemMessage component * refactor: normalize ChatTextField component * refactor: normalize ChatUserBadge component * refactor: normalize ChatUserMessage component * refactor: normalize ContentHeader component * refactor: normalize OwncastLogo component * refactor: normalize UserDropdown component * chore(eslint): modify react/function-component-definition rule * refactor: normalize CodecSelector component * refactor: update a bunch of functional components using eslint * refactor: update a bunch of functional components using eslint, pt2 * refactor: update a bunch of functional components using eslint, pt3 * refactor: replace all component->component default imports with named imports * refactor: replace all component-stories->component default imports with named imports * refactor: remove default exports from most components * chore(eslint): add eslint config files for the components and pages dirs * fix: use-before-define error in ChatContainer * Fix ChatContainer import * Only process .tsx files in Next builds Co-authored-by: Gabe Kangas <gabek@real-ity.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatActionMessage from './ChatActionMessage';
|
||||
import { ChatActionMessage } from './ChatActionMessage';
|
||||
import Mock from '../../../stories/assets/mocks/chatmessage-action.png';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import s from './ChatActionMessage.module.scss';
|
||||
import { FC } from 'react';
|
||||
import styles from './ChatActionMessage.module.scss';
|
||||
|
||||
/* eslint-disable react/no-danger */
|
||||
interface Props {
|
||||
export type ChatActionMessageProps = {
|
||||
body: string;
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function ChatActionMessage(props: Props) {
|
||||
const { body } = props;
|
||||
|
||||
return <div dangerouslySetInnerHTML={{ __html: body }} className={s.chatAction} />;
|
||||
}
|
||||
export const ChatActionMessage: FC<ChatActionMessageProps> = ({ body }) => (
|
||||
<div dangerouslySetInnerHTML={{ __html: body }} className={styles.chatAction} />
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatContainer from './index';
|
||||
import { ChatContainer } from './ChatContainer';
|
||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,32 +1,77 @@
|
||||
import { Button } from 'antd';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { useState, useMemo, useRef, CSSProperties } from 'react';
|
||||
import { useState, useMemo, useRef, CSSProperties, FC } from 'react';
|
||||
import { EditFilled, VerticalAlignBottomOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
ConnectedClientInfoEvent,
|
||||
MessageType,
|
||||
NameChangeEvent,
|
||||
} from '../../../interfaces/socket-events';
|
||||
import s from './ChatContainer.module.scss';
|
||||
import styles from './ChatContainer.module.scss';
|
||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||
import { ChatTextField, ChatUserMessage } from '..';
|
||||
import ChatModeratorNotification from '../ChatModeratorNotification/ChatModeratorNotification';
|
||||
import { ChatUserMessage } from '../ChatUserMessage/ChatUserMessage';
|
||||
import { ChatTextField } from '../ChatTextField/ChatTextField';
|
||||
import { ChatModeratorNotification } from '../ChatModeratorNotification/ChatModeratorNotification';
|
||||
// import ChatActionMessage from '../ChatAction/ChatActionMessage';
|
||||
import ChatSystemMessage from '../ChatSystemMessage/ChatSystemMessage';
|
||||
import ChatJoinMessage from '../ChatJoinMessage/ChatJoinMessage';
|
||||
import { ChatSystemMessage } from '../ChatSystemMessage/ChatSystemMessage';
|
||||
import { ChatJoinMessage } from '../ChatJoinMessage/ChatJoinMessage';
|
||||
|
||||
interface Props {
|
||||
export type ChatContainerProps = {
|
||||
messages: ChatMessage[];
|
||||
usernameToHighlight: string;
|
||||
chatUserId: string;
|
||||
isModerator: boolean;
|
||||
showInput?: boolean;
|
||||
height?: string;
|
||||
};
|
||||
|
||||
function shouldCollapseMessages(messages: ChatMessage[], index: number): boolean {
|
||||
if (messages.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = messages[index];
|
||||
const {
|
||||
user: { id },
|
||||
} = message;
|
||||
const lastMessage = messages[index - 1];
|
||||
if (lastMessage?.type !== MessageType.CHAT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lastMessage.timestamp || !message.timestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxTimestampDelta = 1000 * 60 * 2; // 2 minutes
|
||||
const lastTimestamp = new Date(lastMessage.timestamp).getTime();
|
||||
const thisTimestamp = new Date(message.timestamp).getTime();
|
||||
if (thisTimestamp - lastTimestamp > maxTimestampDelta) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return id === lastMessage?.user.id;
|
||||
}
|
||||
|
||||
export default function ChatContainer(props: Props) {
|
||||
const { messages, usernameToHighlight, chatUserId, isModerator, showInput, height } = props;
|
||||
function checkIsModerator(message) {
|
||||
const { user } = message;
|
||||
const { scopes } = user;
|
||||
|
||||
if (!scopes || scopes.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return scopes.includes('MODERATOR');
|
||||
}
|
||||
|
||||
export const ChatContainer: FC<ChatContainerProps> = ({
|
||||
messages,
|
||||
usernameToHighlight,
|
||||
chatUserId,
|
||||
isModerator,
|
||||
showInput,
|
||||
height,
|
||||
}) => {
|
||||
const [atBottom, setAtBottom] = useState(false);
|
||||
// const [showButton, setShowButton] = useState(false);
|
||||
const chatContainerRef = useRef(null);
|
||||
@@ -38,13 +83,13 @@ export default function ChatContainer(props: Props) {
|
||||
const color = `var(--theme-color-users-${displayColor})`;
|
||||
|
||||
return (
|
||||
<div className={s.nameChangeView}>
|
||||
<div className={styles.nameChangeView}>
|
||||
<div style={{ marginRight: 5, height: 'max-content', margin: 'auto 5px auto 0' }}>
|
||||
<EditFilled />
|
||||
</div>
|
||||
<div className={s.nameChangeText}>
|
||||
<div className={styles.nameChangeText}>
|
||||
<span style={{ color }}>{oldName}</span>
|
||||
<span className={s.plain}> is now known as </span>
|
||||
<span className={styles.plain}> is now known as </span>
|
||||
<span style={{ color }}>{displayName}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,7 +174,7 @@ export default function ChatContainer(props: Props) {
|
||||
atBottomStateChange={bottom => setAtBottom(bottom)}
|
||||
/>
|
||||
{!atBottom && (
|
||||
<div className={s.toBottomWrap}>
|
||||
<div className={styles.toBottomWrap}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<VerticalAlignBottomOutlined />}
|
||||
@@ -161,46 +206,7 @@ export default function ChatContainer(props: Props) {
|
||||
{showInput && <ChatTextField />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function shouldCollapseMessages(messages: ChatMessage[], index: number): boolean {
|
||||
if (messages.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = messages[index];
|
||||
const {
|
||||
user: { id },
|
||||
} = message;
|
||||
const lastMessage = messages[index - 1];
|
||||
if (lastMessage?.type !== MessageType.CHAT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lastMessage.timestamp || !message.timestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxTimestampDelta = 1000 * 60 * 2; // 2 minutes
|
||||
const lastTimestamp = new Date(lastMessage.timestamp).getTime();
|
||||
const thisTimestamp = new Date(message.timestamp).getTime();
|
||||
if (thisTimestamp - lastTimestamp > maxTimestampDelta) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return id === lastMessage?.user.id;
|
||||
}
|
||||
|
||||
function checkIsModerator(message) {
|
||||
const { user } = message;
|
||||
const { scopes } = user;
|
||||
|
||||
if (!scopes || scopes.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return scopes.includes('MODERATOR');
|
||||
}
|
||||
};
|
||||
|
||||
ChatContainer.defaultProps = {
|
||||
showInput: true,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './ChatContainer';
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatJoinMessage from './ChatJoinMessage';
|
||||
import { ChatJoinMessage } from './ChatJoinMessage';
|
||||
import Mock from '../../../stories/assets/mocks/chatmessage-action.png';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import s from './ChatJoinMessage.module.scss';
|
||||
import ChatUserBadge from '../ChatUserBadge/ChatUserBadge';
|
||||
import { FC } from 'react';
|
||||
import styles from './ChatJoinMessage.module.scss';
|
||||
import { ChatUserBadge } from '../ChatUserBadge/ChatUserBadge';
|
||||
|
||||
interface Props {
|
||||
export type ChatJoinMessageProps = {
|
||||
isAuthorModerator: boolean;
|
||||
userColor: number;
|
||||
displayName: string;
|
||||
}
|
||||
};
|
||||
|
||||
export default function ChatJoinMessage(props: Props) {
|
||||
const { isAuthorModerator, userColor, displayName } = props;
|
||||
export const ChatJoinMessage: FC<ChatJoinMessageProps> = ({
|
||||
isAuthorModerator,
|
||||
userColor,
|
||||
displayName,
|
||||
}) => {
|
||||
const color = `var(--theme-user-colors-${userColor})`;
|
||||
|
||||
return (
|
||||
<div className={s.join}>
|
||||
<div className={styles.join}>
|
||||
<span style={{ color }}>
|
||||
{displayName}
|
||||
{isAuthorModerator && (
|
||||
@@ -24,4 +28,4 @@ export default function ChatJoinMessage(props: Props) {
|
||||
joined the chat.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatModerationActionMenu from './ChatModerationActionMenu';
|
||||
import { ChatModerationActionMenu } from './ChatModerationActionMenu';
|
||||
|
||||
const mocks = {
|
||||
mocks: [
|
||||
@@ -82,7 +82,7 @@ export default {
|
||||
} as ComponentMeta<typeof ChatModerationActionMenu>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ChatModerationActionMenu> = args => (
|
||||
const Template: ComponentStory<typeof ChatModerationActionMenu> = () => (
|
||||
<RecoilRoot>
|
||||
<ChatModerationActionMenu
|
||||
accessToken="abc123"
|
||||
|
||||
@@ -5,22 +5,26 @@ import {
|
||||
SmallDashOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Dropdown, Menu, MenuProps, Space, Modal, message } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import ChatModerationDetailsModal from '../ChatModerationDetailsModal/ChatModerationDetailsModal';
|
||||
import s from './ChatModerationActionMenu.module.scss';
|
||||
import { FC, useState } from 'react';
|
||||
import { ChatModerationDetailsModal } from '../ChatModerationDetailsModal/ChatModerationDetailsModal';
|
||||
import styles from './ChatModerationActionMenu.module.scss';
|
||||
import ChatModeration from '../../../services/moderation-service';
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
interface Props {
|
||||
export type ChatModerationActionMenuProps = {
|
||||
accessToken: string;
|
||||
messageID: string;
|
||||
userID: string;
|
||||
userDisplayName: string;
|
||||
}
|
||||
};
|
||||
|
||||
export default function ChatModerationActionMenu(props: Props) {
|
||||
const { messageID, userID, userDisplayName, accessToken } = props;
|
||||
export const ChatModerationActionMenu: FC<ChatModerationActionMenuProps> = ({
|
||||
messageID,
|
||||
userID,
|
||||
userDisplayName,
|
||||
accessToken,
|
||||
}) => {
|
||||
const [showUserDetailsModal, setShowUserDetailsModal] = useState(false);
|
||||
|
||||
const handleBanUser = async () => {
|
||||
@@ -78,7 +82,7 @@ export default function ChatModerationActionMenu(props: Props) {
|
||||
{
|
||||
label: (
|
||||
<div>
|
||||
<span className={s.icon}>
|
||||
<span className={styles.icon}>
|
||||
<EyeInvisibleOutlined />
|
||||
</span>
|
||||
Hide Message
|
||||
@@ -89,7 +93,7 @@ export default function ChatModerationActionMenu(props: Props) {
|
||||
{
|
||||
label: (
|
||||
<div>
|
||||
<span className={s.icon}>
|
||||
<span className={styles.icon}>
|
||||
<CloseCircleOutlined />
|
||||
</span>
|
||||
Ban User
|
||||
@@ -127,4 +131,4 @@ export default function ChatModerationActionMenu(props: Props) {
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatModerationDetailsModal from './ChatModerationDetailsModal';
|
||||
import { ChatModerationDetailsModal } from './ChatModerationDetailsModal';
|
||||
|
||||
const mocks = {
|
||||
mocks: [
|
||||
@@ -82,7 +82,7 @@ export default {
|
||||
} as ComponentMeta<typeof ChatModerationDetailsModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ChatModerationDetailsModal> = args => (
|
||||
const Template: ComponentStory<typeof ChatModerationDetailsModal> = () => (
|
||||
<RecoilRoot>
|
||||
<ChatModerationDetailsModal userId="testuser123" accessToken="fakeaccesstoken4839" />
|
||||
</RecoilRoot>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Button, Col, Row, Spin } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import ChatModeration from '../../../services/moderation-service';
|
||||
import s from './ChatModerationDetailsModal.module.scss';
|
||||
import styles from './ChatModerationDetailsModal.module.scss';
|
||||
|
||||
interface Props {
|
||||
export type ChatModerationDetailsModalProps = {
|
||||
userId: string;
|
||||
accessToken: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface UserDetails {
|
||||
user: User;
|
||||
@@ -91,7 +91,7 @@ const UserColorBlock = ({ color }) => {
|
||||
<Row justify="space-around" align="middle">
|
||||
<Col span={12}>Color</Col>
|
||||
<Col span={12}>
|
||||
<div className={s.colorBlock} style={{ backgroundColor: bg }}>
|
||||
<div className={styles.colorBlock} style={{ backgroundColor: bg }}>
|
||||
{color}
|
||||
</div>
|
||||
</Col>
|
||||
@@ -99,8 +99,10 @@ const UserColorBlock = ({ color }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default function ChatModerationDetailsModal(props: Props) {
|
||||
const { userId, accessToken } = props;
|
||||
export const ChatModerationDetailsModal: FC<ChatModerationDetailsModalProps> = ({
|
||||
userId,
|
||||
accessToken,
|
||||
}) => {
|
||||
const [userDetails, setUserDetails] = useState<UserDetails | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -127,7 +129,7 @@ export default function ChatModerationDetailsModal(props: Props) {
|
||||
user;
|
||||
|
||||
return (
|
||||
<div className={s.modalContainer}>
|
||||
<div className={styles.modalContainer}>
|
||||
<Spin spinning={loading}>
|
||||
<h1>{displayName}</h1>
|
||||
<Row justify="space-around" align="middle">
|
||||
@@ -161,7 +163,7 @@ export default function ChatModerationDetailsModal(props: Props) {
|
||||
<div>
|
||||
<h1>Recent Chat Messages</h1>
|
||||
|
||||
<div className={s.chatHistory}>
|
||||
<div className={styles.chatHistory}>
|
||||
{messages.map(message => (
|
||||
<ChatMessageRow
|
||||
key={message.id}
|
||||
@@ -176,4 +178,4 @@ export default function ChatModerationDetailsModal(props: Props) {
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatModeratorNotification from './ChatModeratorNotification';
|
||||
import { ChatModeratorNotification } from './ChatModeratorNotification';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/Moderation Role Notification',
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import s from './ChatModeratorNotification.module.scss';
|
||||
import styles from './ChatModeratorNotification.module.scss';
|
||||
import Icon from '../../../assets/images/moderator.svg';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function ModeratorNotification() {
|
||||
return (
|
||||
<div className={s.chatModerationNotification}>
|
||||
<Icon className={s.icon} />
|
||||
You are now a moderator.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export const ChatModeratorNotification = () => (
|
||||
<div className={styles.chatModerationNotification}>
|
||||
<Icon className={styles.icon} />
|
||||
You are now a moderator.
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatSocialMessage from './ChatSocialMessage';
|
||||
import { ChatSocialMessage } from './ChatSocialMessage';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/Social-fediverse event',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// TODO remove unused props
|
||||
import { FC } from 'react';
|
||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||
|
||||
interface Props {
|
||||
export interface ChatSocialMessageProps {
|
||||
message: ChatMessage;
|
||||
}
|
||||
|
||||
export default function ChatSocialMessage(props: Props) {
|
||||
return <div>Component goes here</div>;
|
||||
}
|
||||
export const ChatSocialMessage: FC<ChatSocialMessageProps> = () => <div>Component goes here</div>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatSystemMessage from './ChatSystemMessage';
|
||||
import { ChatSystemMessage } from './ChatSystemMessage';
|
||||
import Mock from '../../../stories/assets/mocks/chatmessage-system.png';
|
||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import { Highlight } from 'react-highlighter-ts';
|
||||
import { FC } from 'react';
|
||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||
import s from './ChatSystemMessage.module.scss';
|
||||
import styles from './ChatSystemMessage.module.scss';
|
||||
|
||||
interface Props {
|
||||
export type ChatSystemMessageProps = {
|
||||
message: ChatMessage;
|
||||
highlightString: string;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function ChatSystemMessage({ message, highlightString }: Props) {
|
||||
const { body, user } = message;
|
||||
const { displayName } = user;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={s.chatSystemMessage}>
|
||||
<div className={s.user}>
|
||||
<span className={s.userName}>{displayName}</span>
|
||||
</div>
|
||||
<Highlight search={highlightString}>
|
||||
<div className={s.message} dangerouslySetInnerHTML={{ __html: body }} />
|
||||
</Highlight>
|
||||
export const ChatSystemMessage: FC<ChatSystemMessageProps> = ({
|
||||
message: {
|
||||
body,
|
||||
user: { displayName },
|
||||
},
|
||||
highlightString,
|
||||
}) => (
|
||||
<div className={styles.chatSystemMessage}>
|
||||
<div className={styles.user}>
|
||||
<span className={styles.userName}>{displayName}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Highlight search={highlightString}>
|
||||
<div className={styles.message} dangerouslySetInnerHTML={{ __html: body }} />
|
||||
</Highlight>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatTextField from './ChatTextField';
|
||||
import { ChatTextField } from './ChatTextField';
|
||||
import Mockup from '../../../stories/assets/mocks/chatinput-mock.png';
|
||||
|
||||
const mockResponse = JSON.parse(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { SendOutlined, SmileOutlined } from '@ant-design/icons';
|
||||
import { Button, Popover } from 'antd';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Editor, Node, Path, Transforms, createEditor, BaseEditor, Text, Descendant } from 'slate';
|
||||
import { Transforms, createEditor, BaseEditor, Text, Descendant, Editor, Node, Path } from 'slate';
|
||||
import { Slate, Editable, withReact, ReactEditor, useSelected, useFocused } from 'slate-react';
|
||||
import EmojiPicker from './EmojiPicker';
|
||||
import { EmojiPicker } from './EmojiPicker';
|
||||
import WebsocketService from '../../../services/websocket-service';
|
||||
import { websocketServiceAtom } from '../../stores/ClientConfigStore';
|
||||
import { MessageType } from '../../../interfaces/socket-events';
|
||||
import style from './ChatTextField.module.scss';
|
||||
import styles from './ChatTextField.module.scss';
|
||||
|
||||
type CustomElement = { type: 'paragraph' | 'span'; children: CustomText[] } | ImageNode;
|
||||
type CustomText = { text: string };
|
||||
@@ -90,7 +90,9 @@ const serialize = node => {
|
||||
}
|
||||
};
|
||||
|
||||
export default function ChatTextField() {
|
||||
export type ChatTextFieldProps = {};
|
||||
|
||||
export const ChatTextField: FC<ChatTextFieldProps> = () => {
|
||||
const [showEmojis, setShowEmojis] = useState(false);
|
||||
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
|
||||
const editor = useMemo(() => withReact(withImages(createEditor())), []);
|
||||
@@ -196,14 +198,13 @@ export default function ChatTextField() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={style.root}>
|
||||
<div className={styles.root}>
|
||||
<Slate editor={editor} value={defaultEditorValue}>
|
||||
<Editable
|
||||
onKeyDown={onKeyDown}
|
||||
renderElement={renderElement}
|
||||
placeholder="Chat message goes here..."
|
||||
style={{ width: '100%' }}
|
||||
// onChange={change => setValue(change.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<Popover
|
||||
@@ -221,14 +222,14 @@ export default function ChatTextField() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={style.emojiButton}
|
||||
className={styles.emojiButton}
|
||||
title="Emoji picker button"
|
||||
onClick={() => setShowEmojis(!showEmojis)}
|
||||
>
|
||||
<SmileOutlined />
|
||||
</button>
|
||||
<Button
|
||||
className={style.sendButton}
|
||||
className={styles.sendButton}
|
||||
size="large"
|
||||
type="ghost"
|
||||
icon={<SendOutlined />}
|
||||
@@ -237,4 +238,4 @@ export default function ChatTextField() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function EmojiPicker(props: Props) {
|
||||
export const EmojiPicker = (props: Props) => {
|
||||
const [customEmoji, setCustomEmoji] = useState([]);
|
||||
const { onEmojiSelect, onCustomEmojiSelect } = props;
|
||||
const ref = useRef();
|
||||
@@ -54,4 +54,4 @@ export default function EmojiPicker(props: Props) {
|
||||
}, [customEmoji]);
|
||||
|
||||
return <div ref={ref} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatUserBadge from './ChatUserBadge';
|
||||
import { ChatUserBadge } from './ChatUserBadge';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/User Flag',
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import React from 'react';
|
||||
import s from './ChatUserBadge.module.scss';
|
||||
import React, { FC } from 'react';
|
||||
import styles from './ChatUserBadge.module.scss';
|
||||
|
||||
interface Props {
|
||||
export type ChatUserBadgeProps = {
|
||||
badge: React.ReactNode;
|
||||
userColor: number;
|
||||
}
|
||||
};
|
||||
|
||||
export default function ChatUserBadge(props: Props) {
|
||||
const { badge, userColor } = props;
|
||||
export const ChatUserBadge: FC<ChatUserBadgeProps> = ({ badge, userColor }) => {
|
||||
const color = `var(--theme-user-colors-${userColor})`;
|
||||
const style = { color, borderColor: color };
|
||||
|
||||
return (
|
||||
<span style={style} className={s.badge}>
|
||||
<span style={style} className={styles.badge}>
|
||||
{badge}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatUserMessage from './index';
|
||||
import { ChatUserMessage } from './ChatUserMessage';
|
||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||
import Mock from '../../../stories/assets/mocks/chatmessage-user.png';
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Highlight } from 'react-highlighter-ts';
|
||||
import he from 'he';
|
||||
import cn from 'classnames';
|
||||
import { Tooltip } from 'antd';
|
||||
import { LinkOutlined } from '@ant-design/icons';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import s from './ChatUserMessage.module.scss';
|
||||
import styles from './ChatUserMessage.module.scss';
|
||||
import { formatTimestamp } from './messageFmt';
|
||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||
import ChatModerationActionMenu from '../ChatModerationActionMenu/ChatModerationActionMenu';
|
||||
import ChatUserBadge from '../ChatUserBadge/ChatUserBadge';
|
||||
import { ChatModerationActionMenu } from '../ChatModerationActionMenu/ChatModerationActionMenu';
|
||||
import { ChatUserBadge } from '../ChatUserBadge/ChatUserBadge';
|
||||
import { accessTokenAtom } from '../../stores/ClientConfigStore';
|
||||
|
||||
interface Props {
|
||||
export type ChatUserMessageProps = {
|
||||
message: ChatMessage;
|
||||
showModeratorMenu: boolean;
|
||||
highlightString: string;
|
||||
@@ -21,9 +21,9 @@ interface Props {
|
||||
sameUserAsLast: boolean;
|
||||
isAuthorModerator: boolean;
|
||||
isAuthorAuthenticated: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export default function ChatUserMessage({
|
||||
export const ChatUserMessage: FC<ChatUserMessageProps> = ({
|
||||
message,
|
||||
highlightString,
|
||||
showModeratorMenu,
|
||||
@@ -31,7 +31,7 @@ export default function ChatUserMessage({
|
||||
sameUserAsLast,
|
||||
isAuthorModerator,
|
||||
isAuthorAuthenticated,
|
||||
}: Props) {
|
||||
}) => {
|
||||
const { id: messageId, body, user, timestamp } = message;
|
||||
const { id: userId, displayName, displayColor } = user;
|
||||
const accessToken = useRecoilValue<string>(accessTokenAtom);
|
||||
@@ -59,29 +59,32 @@ export default function ChatUserMessage({
|
||||
}, [message]);
|
||||
|
||||
return (
|
||||
<div className={cn(s.messagePadding, sameUserAsLast && s.messagePaddingCollapsed)}>
|
||||
<div className={cn(styles.messagePadding, sameUserAsLast && styles.messagePaddingCollapsed)}>
|
||||
<div
|
||||
className={cn(s.root, {
|
||||
[s.ownMessage]: sentBySelf,
|
||||
className={cn(styles.root, {
|
||||
[styles.ownMessage]: sentBySelf,
|
||||
})}
|
||||
style={{ borderColor: color }}
|
||||
>
|
||||
{!sameUserAsLast && (
|
||||
<Tooltip title="user info goes here" placement="topLeft" mouseEnterDelay={1}>
|
||||
<div className={s.user} style={{ color }}>
|
||||
<span className={s.userName}>{displayName}</span>
|
||||
<div className={styles.user} style={{ color }}>
|
||||
<span className={styles.userName}>{displayName}</span>
|
||||
<span>{badgeNodes}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={formattedTimestamp} mouseEnterDelay={1}>
|
||||
<Highlight search={highlightString}>
|
||||
<div className={s.message} dangerouslySetInnerHTML={{ __html: formattedMessage }} />
|
||||
<div
|
||||
className={styles.message}
|
||||
dangerouslySetInnerHTML={{ __html: formattedMessage }}
|
||||
/>
|
||||
</Highlight>
|
||||
</Tooltip>
|
||||
|
||||
{showModeratorMenu && (
|
||||
<div className={s.modMenuWrapper}>
|
||||
<div className={styles.modMenuWrapper}>
|
||||
<ChatModerationActionMenu
|
||||
messageID={messageId}
|
||||
accessToken={accessToken}
|
||||
@@ -90,9 +93,9 @@ export default function ChatUserMessage({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={s.customBorder} style={{ color }} />
|
||||
<div className={s.background} style={{ color }} />
|
||||
<div className={styles.customBorder} style={{ color }} />
|
||||
<div className={styles.background} style={{ color }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './ChatUserMessage';
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as ChatContainer } from './ChatContainer';
|
||||
export { default as ChatUserMessage } from './ChatUserMessage';
|
||||
export { default as ChatTextField } from './ChatTextField/ChatTextField';
|
||||
Reference in New Issue
Block a user