diff --git a/.github/workflows/javascript-formatting.yml b/.github/workflows/javascript-formatting.yml index 272d2b34e..c15e2ce5f 100644 --- a/.github/workflows/javascript-formatting.yml +++ b/.github/workflows/javascript-formatting.yml @@ -23,7 +23,7 @@ jobs: uses: creyD/prettier_action@v4.2 with: # This part is also where you can pass other options, for example: - prettier_options: --write webroot/**/*.{js,md} + prettier_options: --write web/**/*.{js,ts,jsx,tsx,css,md} working_directory: web only_changed: true env: diff --git a/web/.eslintrc.js b/web/.eslintrc.js index 18ecdce20..65db95de3 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -40,6 +40,7 @@ module.exports = { '@typescript-eslint/no-use-before-define': [1], 'no-shadow': 'off', '@typescript-eslint/no-shadow': ['error'], + 'no-restricted-exports': 'off', 'react/jsx-no-target-blank': [ 1, { diff --git a/web/.prettierrc b/web/.prettierrc index a917a6dd8..040711442 100644 --- a/web/.prettierrc +++ b/web/.prettierrc @@ -5,4 +5,4 @@ "singleQuote": true, "trailingComma": "all", "arrowParens": "avoid" -} \ No newline at end of file +} diff --git a/web/.vscode/settings.json b/web/.vscode/settings.json index 97824caab..02233c300 100644 --- a/web/.vscode/settings.json +++ b/web/.vscode/settings.json @@ -1,13 +1,13 @@ { - "cSpell.words": [ - "Owncast", - "antd", - "bitrates", - "chartkick", - "framerates", - "kbps", - "linkify", - "paypal", - "toggleswitch" - ] -} \ No newline at end of file + "cSpell.words": [ + "Owncast", + "antd", + "bitrates", + "chartkick", + "framerates", + "kbps", + "linkify", + "paypal", + "toggleswitch" + ] +} diff --git a/web/README.md b/web/README.md index debfe7fa4..339f9f7f9 100644 --- a/web/README.md +++ b/web/README.md @@ -8,21 +8,21 @@ The Owncast web frontend is a [Next.js](https://nextjs.org/) project with [React **First**, install the dependencies. -```npm install --include=dev``` +`npm install --include=dev` ### Run the web project Make sure you're running an instance of Owncast on localhost:8080, as your copy of the admin will look to use that as the API. **Next**, start the web project with npm. - - ```npm run dev``` + +`npm run dev` ### Components and Styles You can start the [Storybook](https://storybook.js.org/) UI for exploring, testing, and developing components by running: -```npm run storybook``` +`npm run storybook` This allows for components to be made available without the need of the server to be running and changes to be made in isolation. @@ -63,4 +63,4 @@ We are currently experimenting with using [Storybook](https://storybook.js.org/) To work with Storybook: -```npm run storybook``` \ No newline at end of file +`npm run storybook` diff --git a/web/components/CustomPageContent.tsx b/web/components/CustomPageContent.tsx index dae759bb2..147e9c2ad 100644 --- a/web/components/CustomPageContent.tsx +++ b/web/components/CustomPageContent.tsx @@ -4,5 +4,6 @@ interface Props { export default function CustomPageContent(props: Props) { const { content } = props; + // eslint-disable-next-line react/no-danger return
; } diff --git a/web/components/PageLogo.tsx b/web/components/PageLogo.tsx index 9e9c4cabe..4535a579b 100644 --- a/web/components/PageLogo.tsx +++ b/web/components/PageLogo.tsx @@ -1,7 +1,3 @@ -interface Props { - url: string; -} - -export default function PageLogo(props: Props) { +export default function PageLogo() { return
Pimary logo component goes here
; } diff --git a/web/components/SocialLinks.tsx b/web/components/SocialLinks.tsx index 7da70e602..83320859c 100644 --- a/web/components/SocialLinks.tsx +++ b/web/components/SocialLinks.tsx @@ -1,9 +1,11 @@ import { SocialLink } from '../interfaces/social-link.model'; interface Props { + // eslint-disable-next-line react/no-unused-prop-types links: SocialLink[]; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars export default function SocialLinks(props: Props) { return
Social links component goes here
; } diff --git a/web/components/action-buttons/ActionButton.module.scss b/web/components/action-buttons/ActionButton.module.scss index f498d8750..c3e3df623 100644 --- a/web/components/action-buttons/ActionButton.module.scss +++ b/web/components/action-buttons/ActionButton.module.scss @@ -4,4 +4,4 @@ width: 20px; margin-right: 3px; } -} \ No newline at end of file +} diff --git a/web/components/action-buttons/ActionButton.tsx b/web/components/action-buttons/ActionButton.tsx index 41002519f..bce8cae26 100644 --- a/web/components/action-buttons/ActionButton.tsx +++ b/web/components/action-buttons/ActionButton.tsx @@ -1,7 +1,7 @@ import { Button } from 'antd'; import { useState } from 'react'; import Modal from '../ui/Modal/Modal'; -import { ExternalAction } from '../interfaces/external-action.interface'; +import { ExternalAction } from '../../interfaces/external-action'; import s from './ActionButton.module.scss'; interface Props { diff --git a/web/components/chat/ChatActionMessage.jsx b/web/components/chat/ChatActionMessage.tsx similarity index 64% rename from web/components/chat/ChatActionMessage.jsx rename to web/components/chat/ChatActionMessage.tsx index 69dd2f0e1..120e2aaf6 100644 --- a/web/components/chat/ChatActionMessage.jsx +++ b/web/components/chat/ChatActionMessage.tsx @@ -1,9 +1,11 @@ import { ChatMessage } from '../../interfaces/chat-message.model'; interface Props { + // eslint-disable-next-line react/no-unused-prop-types message: ChatMessage; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars export default function ChatSystemMessage(props: Props) { return
Component goes here
; } diff --git a/web/components/chat/ChatContainer.tsx b/web/components/chat/ChatContainer.tsx index 142e15c7e..7c23f34c7 100644 --- a/web/components/chat/ChatContainer.tsx +++ b/web/components/chat/ChatContainer.tsx @@ -1,6 +1,6 @@ import { Spin } from 'antd'; import { Virtuoso } from 'react-virtuoso'; -import { useState, useMemo, useCallback, useEffect, useRef } from 'react'; +import { useRef } from 'react'; import { LoadingOutlined } from '@ant-design/icons'; import { ChatMessage } from '../../interfaces/chat-message.model'; import { ChatState } from '../../interfaces/application-state'; diff --git a/web/components/chat/ChatModeratorNotification.tsx b/web/components/chat/ChatModeratorNotification.tsx index 3d38f4248..f6e39f4fe 100644 --- a/web/components/chat/ChatModeratorNotification.tsx +++ b/web/components/chat/ChatModeratorNotification.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ interface Props {} export default function ChatModerationNotification(props: Props) { diff --git a/web/components/chat/ChatSocialMessage.jsx b/web/components/chat/ChatSocialMessage.tsx similarity index 67% rename from web/components/chat/ChatSocialMessage.jsx rename to web/components/chat/ChatSocialMessage.tsx index 53f4f2fde..1ce8f0095 100644 --- a/web/components/chat/ChatSocialMessage.jsx +++ b/web/components/chat/ChatSocialMessage.tsx @@ -1,3 +1,5 @@ +/* eslint-disable react/no-unused-prop-types */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { ChatMessage } from '../../interfaces/chat-message.model'; interface Props { diff --git a/web/components/chat/ChatSystemMessage.tsx b/web/components/chat/ChatSystemMessage.tsx index 69dd2f0e1..a4459e51c 100644 --- a/web/components/chat/ChatSystemMessage.tsx +++ b/web/components/chat/ChatSystemMessage.tsx @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable react/no-unused-prop-types */ import { ChatMessage } from '../../interfaces/chat-message.model'; interface Props { diff --git a/web/components/chat/ChatTextField.tsx b/web/components/chat/ChatTextField.tsx index b4cbf31dd..5d2ef62c0 100644 --- a/web/components/chat/ChatTextField.tsx +++ b/web/components/chat/ChatTextField.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; interface Props {} +// eslint-disable-next-line @typescript-eslint/no-unused-vars export default function ChatTextField(props: Props) { const [value, setValue] = useState(''); const [showEmojis, setShowEmojis] = useState(false); diff --git a/web/components/chat/ChatTextField/ChatTextField.module.scss b/web/components/chat/ChatTextField/ChatTextField.module.scss index 4e386a287..327f08fdc 100644 --- a/web/components/chat/ChatTextField/ChatTextField.module.scss +++ b/web/components/chat/ChatTextField/ChatTextField.module.scss @@ -1,4 +1,4 @@ .root { - height: 2rem; - color: var(--black); -} \ No newline at end of file + height: 2rem; + color: var(--black); +} diff --git a/web/components/chat/ChatTextField/ChatTextField.tsx b/web/components/chat/ChatTextField/ChatTextField.tsx index e5876b69e..1989c35ce 100644 --- a/web/components/chat/ChatTextField/ChatTextField.tsx +++ b/web/components/chat/ChatTextField/ChatTextField.tsx @@ -1,14 +1,13 @@ import { SmileOutlined } from '@ant-design/icons'; import { Button, Popover } from 'antd'; -import React, { useState, useMemo, useRef, useEffect } from 'react'; +import React, { useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { Transforms, createEditor, Node, BaseEditor, Text } from 'slate'; +import { Transforms, createEditor, BaseEditor, Text } from 'slate'; import { Slate, Editable, withReact, ReactEditor } from 'slate-react'; import EmojiPicker from './EmojiPicker'; import WebsocketService from '../../../services/websocket-service'; import { websocketServiceAtom } from '../../stores/ClientConfigStore'; import { MessageType } from '../../../interfaces/socket-events'; -import s from './ChatTextField.module.scss'; type CustomElement = { type: 'paragraph'; children: CustomText[] }; type CustomText = { text: string }; @@ -25,24 +24,30 @@ interface Props { value?: string; } +// eslint-disable-next-line react/prop-types const Image = ({ element }) => ( emoji ); +// eslint-disable-next-line @typescript-eslint/no-unused-vars const insertImage = (editor, url) => { - const text = { text: '' }; - const image: ImageElement = { type: 'image', url, children: [text] }; - Transforms.insertNodes(editor, image); + // const text = { text: '' }; + // const image: ImageElement = { type: 'image', url, children: [text] }; + // Transforms.insertNodes(editor, image); }; const withImages = editor => { const { isVoid } = editor; + // eslint-disable-next-line no-param-reassign editor.isVoid = element => (element.type === 'image' ? true : isVoid(element)); + // eslint-disable-next-line no-param-reassign editor.isInline = element => element.type === 'image'; return editor; @@ -52,13 +57,13 @@ export type EmptyText = { text: string; }; -type ImageElement = { - type: 'image'; - url: string; - children: EmptyText[]; -}; +// type ImageElement = { +// type: 'image'; +// url: string; +// children: EmptyText[]; +// }; -const Element = props => { +const Element = (props: any) => { const { attributes, children, element } = props; switch (element.type) { @@ -71,10 +76,10 @@ const Element = props => { const serialize = node => { if (Text.isText(node)) { - let string = node.text; - if (node.bold) { - string = `${string}`; - } + const string = node.text; + // if (node.bold) { + // string = `${string}`; + // } return string; } @@ -90,8 +95,9 @@ const serialize = node => { } }; +// eslint-disable-next-line @typescript-eslint/no-unused-vars export default function ChatTextField(props: Props) { - const { value: originalValue } = props; + // const { value: originalValue } = props; const [showEmojis, setShowEmojis] = useState(false); const websocketService = useRecoilValue(websocketServiceAtom); const [editor] = useState(() => withImages(withReact(createEditor()))); @@ -113,7 +119,7 @@ export default function ChatTextField(props: Props) { Transforms.delete(editor); }; - const handleChange = e => {}; + const handleChange = () => {}; const handleEmojiSelect = emoji => { console.log(emoji); @@ -135,19 +141,12 @@ export default function ChatTextField(props: Props) { } }; - const initialValue = [ - { - type: 'paragraph', - children: [{ text: originalValue }], - }, - ]; - return (
- + } + renderElement={p => } placeholder="Chat message goes here..." /> diff --git a/web/components/chat/ChatTextField/EmojiPicker.tsx b/web/components/chat/ChatTextField/EmojiPicker.tsx index 19fdd6046..bfd1c4bfa 100644 --- a/web/components/chat/ChatTextField/EmojiPicker.tsx +++ b/web/components/chat/ChatTextField/EmojiPicker.tsx @@ -1,22 +1,28 @@ -import data from '@emoji-mart/data'; -import React, { useRef, useEffect } from 'react'; +// import data from '@emoji-mart/data'; +import React, { useRef } from 'react'; -export default function EmojiPicker(props) { +interface Props { + // eslint-disable-next-line react/no-unused-prop-types + onEmojiSelect: (emoji: string) => void; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export default function EmojiPicker(props: Props) { const ref = useRef(); // TODO: Pull this custom emoji data in from the emoji API. - const custom = [ - { - emojis: [ - { - id: 'party_parrot', - name: 'Party Parrot', - keywords: ['dance', 'dancing'], - skins: [{ src: 'https://watch.owncast.online/img/emoji/bluntparrot.gif' }], - }, - ], - }, - ]; + // const custom = [ + // { + // emojis: [ + // { + // id: 'party_parrot', + // name: 'Party Parrot', + // keywords: ['dance', 'dancing'], + // skins: [{ src: 'https://watch.owncast.online/img/emoji/bluntparrot.gif' }], + // }, + // ], + // }, + // ]; // TODO: Fix the emoji picker from throwing errors. // useEffect(() => { diff --git a/web/components/chat/ChatUserMessage.tsx b/web/components/chat/ChatUserMessage.tsx index 95fa77445..9a1df22af 100644 --- a/web/components/chat/ChatUserMessage.tsx +++ b/web/components/chat/ChatUserMessage.tsx @@ -7,7 +7,9 @@ interface Props { export default function ChatUserMessage(props: Props) { const { message, showModeratorMenu } = props; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { body, user, timestamp } = message; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { displayName, displayColor } = user; // TODO: Convert displayColor (a hue) to a usable color. diff --git a/web/components/chat/chat.js b/web/components/chat/chat.js index a1fd14e8a..5436899b4 100644 --- a/web/components/chat/chat.js +++ b/web/components/chat/chat.js @@ -101,45 +101,6 @@ export function convertToText(str = '') { return value; } -/* - You would call this when a user pastes from - the clipboard into a `contenteditable` area. -*/ -export function convertOnPaste(event = { preventDefault() {} }, emojiList) { - // Prevent paste. - event.preventDefault(); - - // Set later. - let value = ''; - - // Does method exist? - const hasEventClipboard = !!( - event.clipboardData && - typeof event.clipboardData === 'object' && - typeof event.clipboardData.getData === 'function' - ); - - // Get clipboard data? - if (hasEventClipboard) { - value = event.clipboardData.getData('text/plain'); - } - - // Insert into temp `