diff options
Diffstat (limited to 'vnext/src/ui')
-rw-r--r-- | vnext/src/ui/Avatar.js | 10 | ||||
-rw-r--r-- | vnext/src/ui/Button.js | 6 | ||||
-rw-r--r-- | vnext/src/ui/Chat.js | 70 | ||||
-rw-r--r-- | vnext/src/ui/Comment.js | 34 | ||||
-rw-r--r-- | vnext/src/ui/Contact.js | 8 | ||||
-rw-r--r-- | vnext/src/ui/Contacts.js | 24 | ||||
-rw-r--r-- | vnext/src/ui/Feeds.js | 118 | ||||
-rw-r--r-- | vnext/src/ui/Header.js | 22 | ||||
-rw-r--r-- | vnext/src/ui/Icon.js | 28 | ||||
-rw-r--r-- | vnext/src/ui/Login.js | 48 | ||||
-rw-r--r-- | vnext/src/ui/Message.js | 66 | ||||
-rw-r--r-- | vnext/src/ui/MessageInput.js | 90 | ||||
-rw-r--r-- | vnext/src/ui/PM.js | 18 | ||||
-rw-r--r-- | vnext/src/ui/Post.js | 52 | ||||
-rw-r--r-- | vnext/src/ui/SearchBox.js | 12 | ||||
-rw-r--r-- | vnext/src/ui/Settings.js | 100 | ||||
-rw-r--r-- | vnext/src/ui/Spinner.js | 10 | ||||
-rw-r--r-- | vnext/src/ui/Thread.js | 130 | ||||
-rw-r--r-- | vnext/src/ui/UploadButton.js | 20 | ||||
-rw-r--r-- | vnext/src/ui/UserInfo.js | 70 | ||||
-rw-r--r-- | vnext/src/ui/Users.js | 22 | ||||
-rw-r--r-- | vnext/src/ui/VisitorContext.js | 16 | ||||
-rw-r--r-- | vnext/src/ui/__tests__/Avatar.test.js | 14 | ||||
-rw-r--r-- | vnext/src/ui/__tests__/MessageInput.test.js | 48 | ||||
-rw-r--r-- | vnext/src/ui/__tests__/UserLink.test.js | 18 | ||||
-rw-r--r-- | vnext/src/ui/helpers/BubbleStyle.js | 8 |
26 files changed, 531 insertions, 531 deletions
diff --git a/vnext/src/ui/Avatar.js b/vnext/src/ui/Avatar.js index 9d93521f..1a8db0c3 100644 --- a/vnext/src/ui/Avatar.js +++ b/vnext/src/ui/Avatar.js @@ -1,7 +1,7 @@ -import { memo } from 'react'; -import { Link } from 'react-router-dom'; +import { memo } from 'react' +import { Link } from 'react-router-dom' -import Icon from './Icon'; +import Icon from './Icon' /** * @typedef {object} AvatarProps @@ -38,7 +38,7 @@ function Avatar({ user, style, link, children }) { {children} </div> </div> - ); + ) } -export default memo(Avatar); +export default memo(Avatar) diff --git a/vnext/src/ui/Button.js b/vnext/src/ui/Button.js index 2c315e46..dd425021 100644 --- a/vnext/src/ui/Button.js +++ b/vnext/src/ui/Button.js @@ -1,4 +1,4 @@ -import { memo } from 'react'; +import { memo } from 'react' /** * @param {import('react').ClassAttributes<HTMLButtonElement> & import('react').ButtonHTMLAttributes<HTMLButtonElement>} props @@ -6,7 +6,7 @@ import { memo } from 'react'; function Button(props) { return ( <button className="Button" {...props} /> - ); + ) } -export default memo(Button); +export default memo(Button) diff --git a/vnext/src/ui/Chat.js b/vnext/src/ui/Chat.js index 4fdeaac7..55b89d09 100644 --- a/vnext/src/ui/Chat.js +++ b/vnext/src/ui/Chat.js @@ -1,17 +1,17 @@ -import { useEffect, useState, useCallback } from 'react'; -import { useParams } from 'react-router-dom'; -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -dayjs.extend(utc); +import { useEffect, useState, useCallback } from 'react' +import { useParams } from 'react-router-dom' +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) -import PM from './PM'; -import MessageInput from './MessageInput'; -import UserInfo from './UserInfo'; +import PM from './PM' +import MessageInput from './MessageInput' +import UserInfo from './UserInfo' -import { getChat, pm } from '../api'; +import { getChat, pm } from '../api' -import { useVisitor } from './VisitorContext'; -import { Helmet } from 'react-helmet'; +import { useVisitor } from './VisitorContext' +import { Helmet } from 'react-helmet' /** * @@ -24,48 +24,48 @@ import { Helmet } from 'react-helmet'; * @param {ChatProps} props */ export default function Chat(props) { - const [visitor] = useVisitor(); - const [messages, setMessages] = useState([]); - const params = useParams(); + const [visitor] = useVisitor() + const [messages, setMessages] = useState([]) + const params = useParams() let loadChat = useCallback((uname) => { - const { hash } = visitor; + const { hash } = visitor if (hash && uname) { getChat(uname) .then(response => { - setMessages(response.data); - }).catch(console.log); + setMessages(response.data) + }).catch(console.log) } - }, [visitor]); + }, [visitor]) let onMessage = useCallback((json) => { - const msg = JSON.parse(json.data); + const msg = JSON.parse(json.data) if (msg.user.uname === params.user) { setMessages((oldChat) => { - return [msg, ...oldChat]; - }); + return [msg, ...oldChat] + }) } - }, [params.user]); + }, [params.user]) let onSend = async ({ body }) => { - let result = false; - let res = await pm(params.user, body).catch(console.error); - result = res.status == 200; - return result; - }; + let result = false + let res = await pm(params.user, body).catch(console.error) + result = res.status == 200 + return result + } useEffect(() => { if (props.connection.addEventListener) { - props.connection.addEventListener('msg', onMessage); + props.connection.addEventListener('msg', onMessage) } - loadChat(params.user); - console.log(props.connection); + loadChat(params.user) + console.log(props.connection) return () => { if (props.connection.removeEventListener) { - props.connection.removeEventListener('msg', onMessage); + props.connection.removeEventListener('msg', onMessage) } - }; - }, [props.connection, onMessage, loadChat, params.user]); - const uname = params.user; + } + }, [props.connection, onMessage, loadChat, params.user]) + const uname = params.user return ( <div className="msg-cont"> <Helmet> @@ -90,5 +90,5 @@ export default function Chat(props) { ) } </div> - ); + ) } diff --git a/vnext/src/ui/Comment.js b/vnext/src/ui/Comment.js index e8fb2afb..6ba34ff0 100644 --- a/vnext/src/ui/Comment.js +++ b/vnext/src/ui/Comment.js @@ -1,14 +1,14 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react' -import MessageInput from './MessageInput'; -import Avatar from './Avatar'; -import { UserLink } from './UserInfo'; -import Button from './Button'; +import MessageInput from './MessageInput' +import Avatar from './Avatar' +import { UserLink } from './UserInfo' +import Button from './Button' -import { format, embedUrls } from '../utils/embed'; +import { format, embedUrls } from '../utils/embed' -import { chatItemStyle } from './helpers/BubbleStyle'; -import { useVisitor } from './VisitorContext'; +import { chatItemStyle } from './helpers/BubbleStyle' +import { useVisitor } from './VisitorContext' /** * @param {{ @@ -23,20 +23,20 @@ import { useVisitor } from './VisitorContext'; * @returns import('react').ReactElement */ export default function Comment({ msg, draft, active, setActive, onStartEditing, postComment }) { - const [visitor] = useVisitor(); + const [visitor] = useVisitor() /** @type {import('react').MutableRefObject<HTMLDivElement?>} */ - const embedRef = useRef(null); + const embedRef = useRef(null) /** @type {import('react').MutableRefObject<HTMLDivElement?>} */ - const msgRef = useRef(null); - const [author] = useState(msg.user); + const msgRef = useRef(null) + const [author] = useState(msg.user) useEffect(() => { if (msgRef.current && embedRef.current) { - embedUrls(msgRef.current.querySelectorAll('a'), embedRef.current); + embedUrls(msgRef.current.querySelectorAll('a'), embedRef.current) if (!embedRef.current.hasChildNodes()) { - embedRef.current.style.display = 'none'; + embedRef.current.style.display = 'none' } } - }, []); + }, []) return ( <div style={chatItemStyle(visitor, msg)}> <div className="msg-header"> @@ -94,11 +94,11 @@ export default function Comment({ msg, draft, active, setActive, onStartEditing, </div> } </div> - ); + ) } /** * @type {import('react').CSSProperties} */ const linkStyle = { cursor: 'pointer' -}; +} diff --git a/vnext/src/ui/Contact.js b/vnext/src/ui/Contact.js index 75c80332..d7bb12c8 100644 --- a/vnext/src/ui/Contact.js +++ b/vnext/src/ui/Contact.js @@ -1,6 +1,6 @@ -import { memo } from 'react'; +import { memo } from 'react' -import Avatar from './Avatar'; +import Avatar from './Avatar' /** * @typedef {object} ContactProps @@ -18,7 +18,7 @@ function Contact({ user, style }) { {user.unreadCount && <span className="badge">{user.unreadCount}</span>} <div className="msg-ts">{user.lastMessageText}</div> </Avatar> - ); + ) } -export default memo(Contact); +export default memo(Contact) diff --git a/vnext/src/ui/Contacts.js b/vnext/src/ui/Contacts.js index b1f87723..1c23f042 100644 --- a/vnext/src/ui/Contacts.js +++ b/vnext/src/ui/Contacts.js @@ -1,23 +1,23 @@ -import { useEffect, useState } from 'react'; -import { Helmet } from 'react-helmet'; +import { useEffect, useState } from 'react' +import { Helmet } from 'react-helmet' -import { getChats } from '../api'; +import { getChats } from '../api' -import Contact from './Contact.js'; -import { ChatSpinner } from './Spinner'; +import Contact from './Contact.js' +import { ChatSpinner } from './Spinner' /** * */ export default function Contacts() { - const [pms, setPms] = useState([]); + const [pms, setPms] = useState([]) useEffect(() => { getChats() .then(response => { - setPms(response.data.pms); - }).catch(console.log); - }, []); + setPms(response.data.pms) + }).catch(console.log) + }, []) return ( <div className="msg-cont"> <Helmet> @@ -31,7 +31,7 @@ export default function Contacts() { } </div> </div> - ); + ) } const chatListStyle = { @@ -39,7 +39,7 @@ const chatListStyle = { flexDirection: 'column', width: '100%', padding: '12px' -}; +} const chatTitleStyle = { width: '100%', @@ -48,4 +48,4 @@ const chatTitleStyle = { background: 'var(--main-background-color)', color: 'var(--text-color)', borderBottom: '1px solid var(--border-color)' -}; +} diff --git a/vnext/src/ui/Feeds.js b/vnext/src/ui/Feeds.js index 48da52e3..086a910e 100644 --- a/vnext/src/ui/Feeds.js +++ b/vnext/src/ui/Feeds.js @@ -1,18 +1,18 @@ -import { useState, useEffect } from 'react'; -import { Link, useLocation, useParams, Navigate, useSearchParams } from 'react-router-dom'; +import { useState, useEffect } from 'react' +import { Link, useLocation, useParams, Navigate, useSearchParams } from 'react-router-dom' -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -dayjs.extend(utc); +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) -import Message from './Message'; -import Spinner from './Spinner'; +import Message from './Message' +import Spinner from './Spinner' -import UserInfo from './UserInfo'; +import UserInfo from './UserInfo' -import { getMessages } from '../api'; -import { useVisitor } from './VisitorContext'; -import { Helmet } from 'react-helmet'; +import { getMessages } from '../api' +import { useVisitor } from './VisitorContext' +import { Helmet } from 'react-helmet' /** * @typedef {object} Query @@ -28,29 +28,29 @@ import { Helmet } from 'react-helmet'; */ function RequireAuth({ children }) { - let location = useLocation(); - let [visitor] = useVisitor(); + let location = useLocation() + let [visitor] = useVisitor() if (!visitor.hash) { // Redirect them to the /login page, but save the current location they were // trying to go to when they were redirected. This allows us to send them // along to that page after they login, which is a nicer user experience // than dropping them off on the home page. - return <Navigate to="/login" state={{ from: location }} />; + return <Navigate to="/login" state={{ from: location }} /> } - return children; + return children } /** * */ export function Discover() { - const [search] = useSearchParams(); + const [search] = useSearchParams() const query = { baseUrl: '/api/messages', search: search, pageParam: search.search ? 'page' : 'before_mid' - }; + } return ( <> <Helmet> @@ -58,7 +58,7 @@ export function Discover() { </Helmet> <Feed query={query} /> </> - ); + ) } /** @@ -68,7 +68,7 @@ export function Discussions() { const query = { baseUrl: '/api/messages/discussions', pageParam: 'to' - }; + } return ( <> <Helmet> @@ -76,26 +76,26 @@ export function Discussions() { </Helmet> <Feed query={query} /> </> - ); + ) } /** * */ export function Blog() { - const { user } = useParams(); - const [params] = useSearchParams(); + const { user } = useParams() + const [params] = useSearchParams() const search = { ...params, uname: user - }; + } const query = { baseUrl: '/api/messages', search: search, pageParam: search.search ? 'page' : 'before_mid' - }; - const blogTitle = `${user} blog`; - const pageTitle = search.tag ? `${blogTitle}: #${search.tag}` : blogTitle; + } + const blogTitle = `${user} blog` + const pageTitle = search.tag ? `${blogTitle}: #${search.tag}` : blogTitle return ( <> <Helmet> @@ -106,22 +106,22 @@ export function Blog() { </div> <Feed query={query} /> </> - ); + ) } /** * */ export function Tag() { - const params = useParams(); - const { tag } = params; + const params = useParams() + const { tag } = params const query = { baseUrl: '/api/messages', search: { tag: tag }, pageParam: 'before_mid' - }; + } return ( <> <Helmet> @@ -129,7 +129,7 @@ export function Tag() { </Helmet> <Feed query={query} /> </> - ); + ) } /** @@ -139,12 +139,12 @@ export function Home() { const query = { baseUrl: '/api/home', pageParam: 'before_mid' - }; + } return ( <RequireAuth> <Feed query={query} /> </RequireAuth> - ); + ) } /** @@ -157,52 +157,52 @@ export function Home() { * @param {FeedState} props */ function Feed({ query }) { - const location = useLocation(); - const [visitor] = useVisitor(); + const location = useLocation() + const [visitor] = useVisitor() const [state, setState] = useState({ hash: visitor.hash, msgs: [], nextpage: null, error: false, tag: '' - }); - const [loading, setLoading] = useState(true); - const [filter] = useSearchParams(); + }) + const [loading, setLoading] = useState(true) + const [filter] = useSearchParams() useEffect(() => { - setLoading(true); + setLoading(true) let getPageParam = (pageParam, lastMessage, /** @type { URLSearchParams } */ filterParams) => { - const pageValue = pageParam === 'before_mid' ? lastMessage.mid : pageParam === 'page' ? (Number(filterParams.page) || 0) + 1 : dayjs.utc(lastMessage.updated).valueOf(); - filterParams.append(pageParam, pageValue); - return `?${filterParams.toString()}`; - }; - let params = { ...Object.fromEntries(filter), ...query.search }; - let url = query.baseUrl; + const pageValue = pageParam === 'before_mid' ? lastMessage.mid : pageParam === 'page' ? (Number(filterParams.page) || 0) + 1 : dayjs.utc(lastMessage.updated).valueOf() + filterParams.append(pageParam, pageValue) + return `?${filterParams.toString()}` + } + let params = { ...Object.fromEntries(filter), ...query.search } + let url = query.baseUrl getMessages(url, params) .then(response => { - const { data } = response; - const { pageParam } = query; - const lastMessage = data.slice(-1)[0] || {}; - const nextpage = getPageParam(pageParam, lastMessage, new URLSearchParams(params)); - document.body.scrollTop = 0; - document.documentElement.scrollTop = 0; + const { data } = response + const { pageParam } = query + const lastMessage = data.slice(-1)[0] || {} + const nextpage = getPageParam(pageParam, lastMessage, new URLSearchParams(params)) + document.body.scrollTop = 0 + document.documentElement.scrollTop = 0 setState((prevState) => { return { ...prevState, msgs: data, nextpage: nextpage, tag: filter['tag'] || '' - }; - }); - setLoading(false); + } + }) + setLoading(false) }).catch(() => { setState((prevState) => { return { ...prevState, error: true - }; - }); - }); - }, [query, filter]); + } + }) + }) + }, [query, filter]) return (state.msgs.length > 0 ? ( <div className="msgs"> { @@ -227,5 +227,5 @@ function Feed({ query }) { } </div> ) : state.error ? <div>error</div> : loading ? <div className="msgs"><Spinner /><Spinner /><Spinner /><Spinner /></div> : <div>No more messages</div> - ); + ) } diff --git a/vnext/src/ui/Header.js b/vnext/src/ui/Header.js index 6cf07844..cee24c67 100644 --- a/vnext/src/ui/Header.js +++ b/vnext/src/ui/Header.js @@ -1,19 +1,19 @@ -import { memo, useCallback } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { memo, useCallback } from 'react' +import { Link, useNavigate } from 'react-router-dom' -import SearchBox from './SearchBox'; +import SearchBox from './SearchBox' function Header() { - const navigate = useNavigate(); + const navigate = useNavigate() /** * @param {string} searchString */ let searchAll = useCallback((searchString) => { - let location = {}; - location.pathname = '/discover'; - location.search = `?search=${searchString}`; - navigate(location); - }, [navigate]); + let location = {} + location.pathname = '/discover' + location.search = `?search=${searchString}` + navigate(location) + }, [navigate]) return ( <div id="header"> <div id="header_wrapper"> @@ -25,7 +25,7 @@ function Header() { </div> </div> </div> - ); + ) } -export default memo(Header); +export default memo(Header) diff --git a/vnext/src/ui/Icon.js b/vnext/src/ui/Icon.js index 6d10df16..bc5ce08a 100644 --- a/vnext/src/ui/Icon.js +++ b/vnext/src/ui/Icon.js @@ -1,7 +1,7 @@ -import { createElement, memo } from 'react'; -import PropTypes from 'prop-types'; +import { createElement, memo } from 'react' +import PropTypes from 'prop-types' -import evilIcons from 'evil-icons/assets/sprite.svg'; +import evilIcons from 'evil-icons/assets/sprite.svg' /** * @typedef {object} IconProps @@ -16,18 +16,18 @@ import evilIcons from 'evil-icons/assets/sprite.svg'; * @param {IconProps} props - icon props */ function IconElement(props) { - var size = props.size ? ' icon--' + props.size : ''; - var className = props.className ? ' ' + props.className : ''; - var klass = 'icon' + (!props.noFill ? ' icon--' + props.name : '') + size + className; + var size = props.size ? ' icon--' + props.size : '' + var className = props.className ? ' ' + props.className : '' + var klass = 'icon' + (!props.noFill ? ' icon--' + props.name : '') + size + className - var name = '#' + props.name + '-icon'; - var useTag = `<use xlink:href='${evilIcons}${name}' />`; - var Icon = createElement('svg', { className: 'icon__cnt', dangerouslySetInnerHTML: { __html: useTag } }); + var name = '#' + props.name + '-icon' + var useTag = `<use xlink:href='${evilIcons}${name}' />` + var Icon = createElement('svg', { className: 'icon__cnt', dangerouslySetInnerHTML: { __html: useTag } }) return createElement( 'div', { className: klass }, wrapSpinner(Icon, klass) - ); + ) } /** @@ -40,17 +40,17 @@ function wrapSpinner(Html, klass) { 'div', { className: 'icon__spinner' }, Html - ); + ) } else { - return Html; + return Html } } -export default memo(IconElement); +export default memo(IconElement) IconElement.propTypes = { size: PropTypes.string.isRequired, name: PropTypes.string.isRequired, className: PropTypes.string, noFill: PropTypes.bool -}; +} diff --git a/vnext/src/ui/Login.js b/vnext/src/ui/Login.js index 73da49c8..5d9908cb 100644 --- a/vnext/src/ui/Login.js +++ b/vnext/src/ui/Login.js @@ -1,13 +1,13 @@ -import { useEffect } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useEffect } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' -import Icon from './Icon'; -import Button from './Button'; -import { useForm } from 'react-hook-form'; +import Icon from './Icon' +import Button from './Button' +import { useForm } from 'react-hook-form' -import { me, facebookLink, vkLink, appleLink } from '../api'; +import { me, facebookLink, vkLink, appleLink } from '../api' -import { useVisitor } from './VisitorContext'; +import { useVisitor } from './VisitorContext' /** * @typedef {object} LoginProps @@ -19,29 +19,29 @@ import { useVisitor } from './VisitorContext'; * @param {LoginProps} props */ function Login({ onAuth }) { - const location = useLocation(); - const navigate = useNavigate(); - const [visitor] = useVisitor(); + const location = useLocation() + const navigate = useNavigate() + const [visitor] = useVisitor() useEffect(() => { if (visitor.hash) { - const {retpath } = location.state || '/'; - console.log(retpath); - navigate(retpath); + const {retpath } = location.state || '/' + console.log(retpath) + navigate(retpath) } - }, [navigate, location.state, visitor]); + }, [navigate, location.state, visitor]) - const { register, handleSubmit } = useForm(); + const { register, handleSubmit } = useForm() /** @type { import('react-hook-form').SubmitHandler<import('react-hook-form').FieldValues> } */ let onSubmit = (values) => { me(values.username, values.password) .then(response => { - onAuth(response); + onAuth(response) } ).catch(ex => { - console.log(ex); - }); - }; + console.log(ex) + }) + } return ( <div className="msg-cont"> <div className="dialoglogin"> @@ -64,25 +64,25 @@ function Login({ onAuth }) { </form> </div> </div> - ); + ) } -export default Login; +export default Login const socialButtonsStyle = { display: 'flex', justifyContent: 'space-evenly', padding: '4px' -}; +} const facebookButtonStyle = { color: '#fff', padding: '2px 14px', background: '#3b5998' -}; +} const vkButtonStyle = { color: '#fff', padding: '2px 14px', background: '#4c75a3' -}; +} diff --git a/vnext/src/ui/Message.js b/vnext/src/ui/Message.js index c5ad175d..e4135700 100644 --- a/vnext/src/ui/Message.js +++ b/vnext/src/ui/Message.js @@ -1,18 +1,18 @@ -import React, { Fragment, memo, useEffect, useRef } from 'react'; +import React, { Fragment, memo, useEffect, useRef } from 'react' -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -dayjs.extend(utc); -import relativeTime from 'dayjs/plugin/relativeTime'; -dayjs.extend(relativeTime); +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) +import relativeTime from 'dayjs/plugin/relativeTime' +dayjs.extend(relativeTime) -import { Link } from 'react-router-dom'; -import Icon from './Icon'; -import Avatar from './Avatar'; -import { UserLink } from './UserInfo'; +import { Link } from 'react-router-dom' +import Icon from './Icon' +import Avatar from './Avatar' +import { UserLink } from './UserInfo' -import { format, embedUrls } from '../utils/embed'; -import { useVisitor } from './VisitorContext'; +import { format, embedUrls } from '../utils/embed' +import { useVisitor } from './VisitorContext' /** * @callback ToggleSubscriptionCallback @@ -31,30 +31,30 @@ import { useVisitor } from './VisitorContext'; * @param {React.PropsWithChildren<{}> & MessageProps} props props */ export default function Message({ data, isThread, onToggleSubscription, children }) { - const [visitor] = useVisitor(); - const isCode = (data.tags || []).indexOf('code') >= 0; - const likesSummary = data.likes ? `${data.likes}` : 'Recommend'; - const commentsSummary = data.replies ? `${data.replies}` : 'Comment'; + const [visitor] = useVisitor() + const isCode = (data.tags || []).indexOf('code') >= 0 + const likesSummary = data.likes ? `${data.likes}` : 'Recommend' + const commentsSummary = data.replies ? `${data.replies}` : 'Comment' /** * @type {React.MutableRefObject<HTMLDivElement?>} */ - const embedRef = useRef(null); + const embedRef = useRef(null) /** * @type {React.MutableRefObject<HTMLDivElement?>} */ - const msgRef = useRef(null); + const msgRef = useRef(null) useEffect(() => { - const msg = msgRef.current; - const embed = embedRef.current; + const msg = msgRef.current + const embed = embedRef.current if (msg && embed) { - embedUrls(msg.querySelectorAll('a'), embed); + embedUrls(msg.querySelectorAll('a'), embed) if (!embed.hasChildNodes()) { - embed.style.display = 'none'; + embed.style.display = 'none' } } - }, []); + }, []) const canComment = data.user && visitor.uid === data.user.uid || !data.ReadOnly && visitor.uid > 0 - || !data.ReadOnly && !isThread; + || !data.ReadOnly && !isThread return ( <div className="msg-cont"> <Recommendations forMessage={data} /> @@ -120,7 +120,7 @@ export default function Message({ data, isThread, onToggleSubscription, children { data.user && canComment && (( isThread ? ( - <a className="msg-button" onClick={() => { onToggleSubscription(data); }}> + <a className="msg-button" onClick={() => { onToggleSubscription(data) }}> { data.subscribed ? (<> <Icon name="ei-check" size="s" /> @@ -143,14 +143,14 @@ export default function Message({ data, isThread, onToggleSubscription, children } {children} </div > - ); + ) } /** * @param {{isCode: boolean, data: {__html: string}}} props props */ function MessageContainer({ isCode, data }) { - return isCode ? (<pre dangerouslySetInnerHTML={data} />) : (<span dangerouslySetInnerHTML={data} />); + return isCode ? (<pre dangerouslySetInnerHTML={data} />) : (<span dangerouslySetInnerHTML={data} />) } /** @@ -171,18 +171,18 @@ function Tags({ data, user }) { )) } </span> - ) : null; + ) : null } -const TagsList = memo(Tags); +const TagsList = memo(Tags) /** * * @param {{forMessage: import('../client').Message}} props props */ function Recommends({ forMessage }) { - const { recommendations } = forMessage; - const likes = forMessage.likes || 0; + const { recommendations } = forMessage + const likes = forMessage.likes || 0 return recommendations && recommendations.length > 0 && ( <div className="msg-recomms">{'♡ by '} { @@ -197,7 +197,7 @@ function Recommends({ forMessage }) { likes > recommendations.length && (<span> and {likes - recommendations.length} others</span>) } </div> - ) || null; + ) || null } -const Recommendations = memo(Recommends); +const Recommendations = memo(Recommends) diff --git a/vnext/src/ui/MessageInput.js b/vnext/src/ui/MessageInput.js index 3d24e728..6ebf4361 100644 --- a/vnext/src/ui/MessageInput.js +++ b/vnext/src/ui/MessageInput.js @@ -1,10 +1,10 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef } from 'react' -import Icon from './Icon'; -import Button from './Button'; +import Icon from './Icon' +import Button from './Button' -import UploadButton from './UploadButton'; -import toast from 'react-hot-toast'; +import UploadButton from './UploadButton' +import toast from 'react-hot-toast' /** @@ -13,13 +13,13 @@ import toast from 'react-hot-toast'; */ function moveCaretToEnd(el) { if (typeof el.selectionStart == 'number') { - el.selectionStart = el.selectionEnd = el.value.length; + el.selectionStart = el.selectionEnd = el.value.length } else if (typeof el.createTextRange != 'undefined') { // Internet Explorer - el.focus(); - var range = el.createTextRange(); - range.collapse(false); - range.select(); + el.focus() + var range = el.createTextRange() + range.collapse(false) + range.select() } } @@ -42,74 +42,74 @@ export default function MessageInput({ text, rows, placeholder, onSend }) { /** * @type {React.MutableRefObject<HTMLTextAreaElement?>} */ - let textareaRef = useRef(null); + let textareaRef = useRef(null) /** * @type {React.MutableRefObject<HTMLInputElement?>} */ - let fileinput = useRef(null); + let fileinput = useRef(null) let updateFocus = () => { - const isDesktop = window.matchMedia('(min-width: 62.5rem)'); + const isDesktop = window.matchMedia('(min-width: 62.5rem)') if (isDesktop.matches) { - const textarea = textareaRef.current; + const textarea = textareaRef.current if (textarea) { - textarea.focus(); - moveCaretToEnd(textarea); + textarea.focus() + moveCaretToEnd(textarea) } } - }; + } useEffect(() => { textChanged({ target: { value: text } - }); - updateFocus(); - }, [text]); + }) + updateFocus() + }, [text]) - let [body, setBody] = useState(text); + let [body, setBody] = useState(text) let handleCtrlEnter = (event) => { if (event.ctrlKey && (event.charCode == 10 || event.charCode == 13)) { - onSubmit({}); + onSubmit({}) } - }; + } const textChanged = (event) => { - setBody(event.target.value); - const el = textareaRef.current; + setBody(event.target.value) + const el = textareaRef.current if (el) { - const offset = el.offsetHeight - el.clientHeight; - const height = el.scrollHeight + offset; - el.style.height = `${height + offset}px`; + const offset = el.offsetHeight - el.clientHeight + const height = el.scrollHeight + offset + el.style.height = `${height + offset}px` } - }; - const [attach, setAttach] = useState(''); + } + const [attach, setAttach] = useState('') let uploadValueChanged = (attach) => { - setAttach(attach); - }; + setAttach(attach) + } let onSubmit = (event) => { if (event.preventDefault) { - event.preventDefault(); + event.preventDefault() } - const input = fileinput.current; + const input = fileinput.current if (input && input.files) { onSend({ body: body, attach: attach ? input.files[0] : '' }).then((success) => { if (success) { - setAttach(''); - setBody(''); + setAttach('') + setBody('') if (textareaRef.current) { - textareaRef.current.style.height = ''; + textareaRef.current.style.height = '' } - updateFocus(); + updateFocus() } else { - toast('Can not update this message'); + toast('Can not update this message') } - }).catch(console.log); + }).catch(console.log) } - }; + } return ( <form className="msg-comment-target" style={{ padding: '12px', width: '100%' }} onSubmit={onSubmit}> <div style={commentStyle}> @@ -122,7 +122,7 @@ export default function MessageInput({ text, rows, placeholder, onSend }) { </div> </div> </form> - ); + ) } /** @@ -133,7 +133,7 @@ const commentStyle = { flexDirection: 'column', width: '100%', marginTop: '10px' -}; +} /** * @type {React.CSSProperties} @@ -143,7 +143,7 @@ const inputBarStyle = { alignItems: 'center', justifyContent: 'space-between', padding: '3px' -}; +} /** * @type {React.CSSProperties} @@ -156,4 +156,4 @@ const textInputStyle = { border: 0, outline: 'none', padding: '4px' -}; +} diff --git a/vnext/src/ui/PM.js b/vnext/src/ui/PM.js index d5a7eff1..3aa877b1 100644 --- a/vnext/src/ui/PM.js +++ b/vnext/src/ui/PM.js @@ -1,13 +1,13 @@ -import { memo } from 'react'; +import { memo } from 'react' -import Avatar from './Avatar'; -import { format } from '../utils/embed'; -import { chatItemStyle } from './helpers/BubbleStyle'; -import { useVisitor } from './VisitorContext'; +import Avatar from './Avatar' +import { format } from '../utils/embed' +import { chatItemStyle } from './helpers/BubbleStyle' +import { useVisitor } from './VisitorContext' function PM(props) { - const { chat } = props; - const [visitor] = useVisitor(); + const { chat } = props + const [visitor] = useVisitor() return ( <li> <div style={chatItemStyle(visitor, chat)}> @@ -17,10 +17,10 @@ function PM(props) { </div> </div> </li> - ); + ) } -export default memo(PM); +export default memo(PM) /* PM.propTypes = { chat: MessageType.isRequired diff --git a/vnext/src/ui/Post.js b/vnext/src/ui/Post.js index 45eceb35..6f7d1e93 100644 --- a/vnext/src/ui/Post.js +++ b/vnext/src/ui/Post.js @@ -1,42 +1,42 @@ -import { useState } from 'react'; -import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; +import { useState } from 'react' +import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' -import Button from './Button'; -import MessageInput from './MessageInput'; +import Button from './Button' +import MessageInput from './MessageInput' -import { post, update } from '../api'; -import { useVisitor } from './VisitorContext'; -import { Helmet } from 'react-helmet'; +import { post, update } from '../api' +import { useVisitor } from './VisitorContext' +import { Helmet } from 'react-helmet' /** * */ export default function Post() { - const location = useLocation(); - const navigate = useNavigate(); - const [visitor] = useVisitor(); - let draftMessage = (location.state || {}).data || {}; - let [draft, setDraft] = useState(draftMessage.body); - let [params] = useSearchParams(); + const location = useLocation() + const navigate = useNavigate() + const [visitor] = useVisitor() + let draftMessage = (location.state || {}).data || {} + let [draft, setDraft] = useState(draftMessage.body) + let [params] = useSearchParams() let postMessage = async ({ attach, body }) => { try { - const res = draftMessage.mid ? await update(draftMessage.mid, 0, body) : await post(body, attach); - let result = res.status == 200; + const res = draftMessage.mid ? await update(draftMessage.mid, 0, body) : await post(body, attach) + let result = res.status == 200 if (result) { - const msg = res.data.newMessage; - navigate(`/${visitor.uname}/${msg.mid}`); + const msg = res.data.newMessage + navigate(`/${visitor.uname}/${msg.mid}`) } - return result; + return result } catch (e) { - console.log(e); + console.log(e) } - return false; - }; + return false + } let appendTag = (tag) => { setDraft(prevDraft => { - return `${prevDraft || ''} *${tag} `; - }); - }; + return `${prevDraft || ''} *${tag} ` + }) + } return ( <div className="msg-cont"> <Helmet> @@ -51,12 +51,12 @@ export default function Post() { <p>Tags:</p> { visitor.tagStats.map(t => { - return (<Button key={t.tag} onClick={() => { appendTag(t.tag); }}>{t.tag}</Button>); + return (<Button key={t.tag} onClick={() => { appendTag(t.tag) }}>{t.tag}</Button>) }) } </div> } </div> - ); + ) } diff --git a/vnext/src/ui/SearchBox.js b/vnext/src/ui/SearchBox.js index 636967b1..e63a19ee 100644 --- a/vnext/src/ui/SearchBox.js +++ b/vnext/src/ui/SearchBox.js @@ -1,4 +1,4 @@ -import { useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form' /** * @typedef {object} SearchBoxPropsFields @@ -13,18 +13,18 @@ import { useForm } from 'react-hook-form'; * @param {SearchBoxProps} props */ function SearchBox({ onSearch }) { - const { register, handleSubmit } = useForm(); + const { register, handleSubmit } = useForm() /** @type { import('react-hook-form').SubmitHandler<import('react-hook-form').FieldValues> } */ let onSubmit = ( values ) => { - onSearch(values.search); - }; + onSearch(values.search) + } return ( <form onSubmit={handleSubmit(onSubmit)}> <input className="text" type="text" placeholder="Search..." {...register('search')} /> <input data-testid="submit" type="submit" hidden /> </form> - ); + ) } -export default SearchBox; +export default SearchBox diff --git a/vnext/src/ui/Settings.js b/vnext/src/ui/Settings.js index 380d8ff6..1205cd49 100644 --- a/vnext/src/ui/Settings.js +++ b/vnext/src/ui/Settings.js @@ -1,46 +1,46 @@ -import { Fragment, useState, useRef } from 'react'; +import { Fragment, useState, useRef } from 'react' -import { me, updateAvatar } from '../api'; +import { me, updateAvatar } from '../api' -import Button from './Button'; -import Icon from './Icon'; -import UploadButton from './UploadButton'; -import Avatar from './Avatar'; -import { useVisitor } from './VisitorContext'; -import { Helmet } from 'react-helmet'; +import Button from './Button' +import Icon from './Icon' +import UploadButton from './UploadButton' +import Avatar from './Avatar' +import { useVisitor } from './VisitorContext' +import { Helmet } from 'react-helmet' /** * @param {{ onChange: Function }} props */ function ChangeAvatarForm({ onChange }) { - const [visitor] = useVisitor(); - const [avatar, setAvatar] = useState(''); - const [preview, setPreview] = useState(); - const avatarInput = useRef(); + const [visitor] = useVisitor() + const [avatar, setAvatar] = useState('') + const [preview, setPreview] = useState() + const avatarInput = useRef() let avatarChanged = (newAvatar) => { - setAvatar(newAvatar); - setPreview(''); + setAvatar(newAvatar) + setPreview('') if (newAvatar) { - let reader = new FileReader(); + let reader = new FileReader() reader.onloadend = (preview) => { - setPreview(preview.target.result); - }; - reader.readAsDataURL(avatarInput.current.files[0]); + setPreview(preview.target.result) + } + reader.readAsDataURL(avatarInput.current.files[0]) } - }; - let previewUser = { ...visitor, uname: '<preview>' }; + } + let previewUser = { ...visitor, uname: '<preview>' } if (preview) { - previewUser = { ...visitor, avatar: preview, uname: '<preview>' }; + previewUser = { ...visitor, avatar: preview, uname: '<preview>' } } let onSubmitAvatar = async (event) => { if (event.preventDefault) { - event.preventDefault(); + event.preventDefault() } - await updateAvatar(avatarInput.current.files[0]); - avatarChanged(''); - let visitor = await me(); - onChange(visitor); - }; + await updateAvatar(avatarInput.current.files[0]) + avatarChanged('') + let visitor = await me() + onChange(visitor) + } return ( <form> <small>Recommendations: PNG, 96x96, <50Kb. Also, JPG and GIF supported.</small> @@ -50,7 +50,7 @@ function ChangeAvatarForm({ onChange }) { <Avatar user={previewUser} /> <Button onClick={onSubmitAvatar}>Update</Button> </form> - ); + ) } /** @@ -58,47 +58,47 @@ function ChangeAvatarForm({ onChange }) { */ export default function Settings({ onChange }) { - const [visitor] = useVisitor(); + const [visitor] = useVisitor() let passwordChanged = () => { - console.log('password changed'); - }; + console.log('password changed') + } let onSubmitPassword = (event) => { if (event.preventDefault) { - event.preventDefault(); + event.preventDefault() } - console.log('password update'); - }; + console.log('password update') + } let emailChanged = () => { - console.log('email update'); - }; + console.log('email update') + } let disableTelegram = () => { - console.log('telegram disable'); - }; + console.log('telegram disable') + } let disableFacebook = (event) => { if (event.preventDefault) { - event.preventDefault(); + event.preventDefault() } - console.log('facebook disable'); - }; + console.log('facebook disable') + } let enableFacebook = (event) => { if (event.preventDefault) { - event.preventDefault(); + event.preventDefault() } - console.log('facebook enable'); - }; + console.log('facebook enable') + } let disableTwitter = () => { - console.log('twitter disable'); - }; + console.log('twitter disable') + } let deleteJid = () => { // TODO - }; + } let addEmail = () => { // TODO - }; + } let deleteEmail = () => { // TODO - }; + } return ( <div className="msg-cont"> <Helmet> @@ -249,7 +249,7 @@ export default function Settings({ onChange }) { </fieldset> </div> - ); + ) } diff --git a/vnext/src/ui/Spinner.js b/vnext/src/ui/Spinner.js index 3e38571e..b611abb8 100644 --- a/vnext/src/ui/Spinner.js +++ b/vnext/src/ui/Spinner.js @@ -1,5 +1,5 @@ -import { memo } from 'react'; -import ContentLoader from 'react-content-loader'; +import { memo } from 'react' +import ContentLoader from 'react-content-loader' function Spinner(props) { return ( @@ -20,10 +20,10 @@ function Spinner(props) { </ContentLoader> </div> </div> - ); + ) } -export default memo(Spinner); +export default memo(Spinner) /** * @@ -42,5 +42,5 @@ export function ChatSpinner(props) { <rect x="56" y="20" rx="0" ry="0" width="85" height="6.4" /> <rect x="0" y="0" rx="0" ry="0" width="48" height="48" /> </ContentLoader> - ); + ) } diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js index a3136c35..b727b73d 100644 --- a/vnext/src/ui/Thread.js +++ b/vnext/src/ui/Thread.js @@ -1,128 +1,128 @@ -import { useEffect, useState, useCallback } from 'react'; -import { useLocation, useParams } from 'react-router-dom'; +import { useEffect, useState, useCallback } from 'react' +import { useLocation, useParams } from 'react-router-dom' -import Comment from './Comment'; -import Message from './Message'; -import MessageInput from './MessageInput'; -import Spinner from './Spinner'; +import Comment from './Comment' +import Message from './Message' +import MessageInput from './MessageInput' +import Spinner from './Spinner' -import { getMessages, comment, update, post } from '../api'; -import { useVisitor } from './VisitorContext'; -import { Helmet } from 'react-helmet'; +import { getMessages, comment, update, post } from '../api' +import { useVisitor } from './VisitorContext' +import { Helmet } from 'react-helmet' /** * @type { import('../api').Message } */ -const emptyMessage = {}; +const emptyMessage = {} /** * Thread component * @param {import('react').PropsWithChildren<{}>} props */ export default function Thread(props) { - const location = useLocation(); - const params = useParams(); - const [message, setMessage] = useState((location.state || {}).data || {}); - const [replies, setReplies] = useState([]); - const [loading, setLoading] = useState(false); - const [active, setActive] = useState(0); + const location = useLocation() + const params = useParams() + const [message, setMessage] = useState((location.state || {}).data || {}) + const [replies, setReplies] = useState([]) + const [loading, setLoading] = useState(false) + const [active, setActive] = useState(0) - const [editing, setEditing] = useState(emptyMessage); - const [visitor] = useVisitor(); - const [hash] = useState(visitor.hash); - const { mid } = params; + const [editing, setEditing] = useState(emptyMessage) + const [visitor] = useVisitor() + const [hash] = useState(visitor.hash) + const { mid } = params let loadReplies = useCallback(() => { - document.body.scrollTop = 0; - document.documentElement.scrollTop = 0; - setReplies([]); - setLoading(true); + document.body.scrollTop = 0 + document.documentElement.scrollTop = 0 + setReplies([]) + setLoading(true) let params = { mid: mid - }; - params.hash = hash; + } + params.hash = hash getMessages('/api/thread', params) .then(response => { - let updatedMessage = response.data.shift(); + let updatedMessage = response.data.shift() if (!message.mid) { - setMessage(updatedMessage); + setMessage(updatedMessage) } - setReplies(response.data); - setLoading(false); - setActive(0); + setReplies(response.data) + setLoading(false) + setActive(0) } ).catch(ex => { - console.log(ex); - }); - }, [hash, message.mid, mid]); + console.log(ex) + }) + }, [hash, message.mid, mid]) let postComment = async ({ body, attach }) => { try { let res = editing.rid ? await update(mid, editing.rid, body) - : await comment(mid, active, body, attach); - let result = res.status == 200; + : await comment(mid, active, body, attach) + let result = res.status == 200 if (result) { - setEditing(emptyMessage); + setEditing(emptyMessage) } - return result; + return result } catch (e) { - console.error(e); + console.error(e) } - return false; - }; + return false + } let startEditing = (reply) => { - setActive(reply.to.rid || 0); - setEditing(reply); - }; + setActive(reply.to.rid || 0) + setEditing(reply) + } useEffect(() => { - setActive(0); - loadReplies(); - }, [loadReplies]); + setActive(0) + loadReplies() + }, [loadReplies]) useEffect(() => { let onReply = (json) => { - const msg = JSON.parse(json.data); + const msg = JSON.parse(json.data) if (msg.mid == message.mid) { setReplies(oldReplies => { - return [...oldReplies, msg]; - }); + return [...oldReplies, msg] + }) setActive(prev => { - return prev + 1; - }); + return prev + 1 + }) } - }; + } if (props.connection.addEventListener && message.mid) { - props.connection.addEventListener('msg', onReply); + props.connection.addEventListener('msg', onReply) } return () => { if (props.connection.removeEventListener && message.mid) { - props.connection.removeEventListener('msg', onReply); + props.connection.removeEventListener('msg', onReply) } - }; - }, [props.connection, message.mid]); + } + }, [props.connection, message.mid]) - const loaders = Math.min(message.replies || 0, 10); - const pageTitle = `${params.user} ${message && message.tags || 'thread'}`; + const loaders = Math.min(message.replies || 0, 10) + const pageTitle = `${params.user} ${message && message.tags || 'thread'}` /** @type { import('./Message').ToggleSubscriptionCallback } */ const handleSubsciptionToggle = (message) => { if (message.subscribed) { if (confirm('Unsubscribe?')) { post(`U #${message.mid}`).then((response) => { if (response.status === 200) { - setMessage({...message, subscribed: false}); + setMessage({...message, subscribed: false}) } - }).catch(console.error); + }).catch(console.error) } } else { if (confirm('Subscribe?')) { post(`S #${message.mid}`).then((response) => { if (response.status === 200) { - setMessage({...message, subscribed: true}); + setMessage({...message, subscribed: true}) } - }).catch(console.error); + }).catch(console.error) } } - }; + } return ( <> <Helmet> @@ -155,5 +155,5 @@ export default function Thread(props) { </ul> } </> - ); + ) } diff --git a/vnext/src/ui/UploadButton.js b/vnext/src/ui/UploadButton.js index b652e522..5ef2fd94 100644 --- a/vnext/src/ui/UploadButton.js +++ b/vnext/src/ui/UploadButton.js @@ -1,4 +1,4 @@ -import Icon from './Icon'; +import Icon from './Icon' /** * @typedef {object} UploadButtonProps @@ -13,20 +13,20 @@ import Icon from './Icon'; */ export default function UploadButton(props) { let openfile = () => { - const input = props.inputRef.current; + const input = props.inputRef.current if (props.value) { - props.onChange(''); + props.onChange('') } else { - input.click(); + input.click() } - }; + } /** * @param {import('react').ChangeEvent<HTMLInputElement>} event */ let attachmentChanged = (event) => { - props.onChange(event.target.value); - }; + props.onChange(event.target.value) + } return ( <div style={props.value ? activeStyle : inactiveStyle} onClick={openfile}> @@ -35,14 +35,14 @@ export default function UploadButton(props) { style={{ display: 'none' }} ref={props.inputRef} value={props.value} onChange={attachmentChanged} /> </div> - ); + ) } const inactiveStyle = { cursor: 'pointer', color: '#888' -}; +} const activeStyle = { cursor: 'pointer', color: 'green' -}; +} diff --git a/vnext/src/ui/UserInfo.js b/vnext/src/ui/UserInfo.js index 2ca8c431..f71dfcdc 100644 --- a/vnext/src/ui/UserInfo.js +++ b/vnext/src/ui/UserInfo.js @@ -1,13 +1,13 @@ -import { memo, useState, useEffect, useRef } from 'react'; -import { Link } from 'react-router-dom'; +import { memo, useState, useEffect, useRef } from 'react' +import { Link } from 'react-router-dom' -import { info, fetchUserUri } from '../api'; +import { info, fetchUserUri } from '../api' -import Avatar from './Avatar'; -import Icon from './Icon'; -import defaultAvatar from '../assets/av-96.png'; +import Avatar from './Avatar' +import Icon from './Icon' +import defaultAvatar from '../assets/av-96.png' -let isMounted; +let isMounted /** * User info component @@ -17,19 +17,19 @@ export default function UserInfo({ uname, onUpdate, children }) { const [user, setUser] = useState({ uname: uname, uid: 0 - }); + }) useEffect(() => { - isMounted = true; + isMounted = true info(uname).then(response => { if (isMounted) { - onUpdate && onUpdate(response.data); - setUser(response.data); + onUpdate && onUpdate(response.data) + setUser(response.data) } - }).catch(console.log); + }).catch(console.log) return () => { - isMounted = false; - }; - }, [onUpdate, uname]); + isMounted = false + } + }, [onUpdate, uname]) return ( <> <div className="userinfo"> @@ -59,7 +59,7 @@ export default function UserInfo({ uname, onUpdate, children }) { </div> {children} </> - ); + ) } /** @@ -67,22 +67,22 @@ export default function UserInfo({ uname, onUpdate, children }) { * @param {{user: import('../api').User}} props */ function Summary({ user }) { - const readUrl = `/${user.uname}/friends`; - const readersUrl = `/${user.uname}/readers`; - const blUrl = `/${user.uname}/bl`; - let read = user.read && <Link key={readUrl} to={readUrl}>I read: {user.read.length}</Link>; - let readers = user.readers && <Link key={readersUrl} to={readersUrl}>My readers: {user.readers.length}</Link>; - let mybl = user.statsMyBL && <Link key={blUrl} to={blUrl}>My blacklist: {user.statsMyBL}</Link>; - let presentItems = [read, readers, mybl].filter(Boolean); + const readUrl = `/${user.uname}/friends` + const readersUrl = `/${user.uname}/readers` + const blUrl = `/${user.uname}/bl` + let read = user.read && <Link key={readUrl} to={readUrl}>I read: {user.read.length}</Link> + let readers = user.readers && <Link key={readersUrl} to={readersUrl}>My readers: {user.readers.length}</Link> + let mybl = user.statsMyBL && <Link key={blUrl} to={blUrl}>My blacklist: {user.statsMyBL}</Link> + let presentItems = [read, readers, mybl].filter(Boolean) return ( <div className="msg-summary"> {presentItems.length > 0 && presentItems.reduce((prev, curr) => [prev, ' ', curr])} </div> - ); + ) } -const UserSummary = memo(Summary); +const UserSummary = memo(Summary) /** @@ -90,10 +90,10 @@ const UserSummary = memo(Summary); * @param {{ user: import('../api').User}} props */ export function UserLink(props) { - const [user, setUser] = useState(props.user); - const userRef = useRef(user); + const [user, setUser] = useState(props.user) + const userRef = useRef(user) useEffect(() => { - isMounted = true; + isMounted = true if (userRef.current.uri) { fetchUserUri(userRef.current.uri).then(remote_user => { if (isMounted) { @@ -102,7 +102,7 @@ export function UserLink(props) { uname: remote_user.preferredUsername, avatar: remote_user.icon && remote_user.icon.url, uri: userRef.current.uri - }); + }) } }).catch(() => { setUser({ @@ -110,13 +110,13 @@ export function UserLink(props) { uname: userRef.current.uri, uri: userRef.current.uri, avatar: defaultAvatar - }); - }); + }) + }) } return () => { - isMounted = false; - }; - }, [props.user]); + isMounted = false + } + }, [props.user]) return ( user.uid ? <Link key={user.uid} to={`/${user.uname}/`} className="info-avatar"> @@ -125,5 +125,5 @@ export function UserLink(props) { : <a href={user.uri} className="info-avatar"> <img src={user.avatar || defaultAvatar} />{user.uname} </a> - ); + ) } diff --git a/vnext/src/ui/Users.js b/vnext/src/ui/Users.js index 32ff2d03..e4fcba1f 100644 --- a/vnext/src/ui/Users.js +++ b/vnext/src/ui/Users.js @@ -1,15 +1,15 @@ -import { useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useState } from 'react' +import { useParams } from 'react-router-dom' -import UserInfo from './UserInfo'; -import Avatar from './Avatar'; -import { Helmet } from 'react-helmet'; +import UserInfo from './UserInfo' +import Avatar from './Avatar' +import { Helmet } from 'react-helmet' /** * Friends feed */ export function Friends() { - const params = useParams(); + const params = useParams() return ( <> <Helmet> @@ -17,14 +17,14 @@ export function Friends() { </Helmet> <Users uname={params.user} prop='read' /> </> - ); + ) } /** * Readers feed */ export function Readers() { - const params = useParams(); + const params = useParams() return ( <> <Helmet> @@ -32,7 +32,7 @@ export function Readers() { </Helmet> <Users uname={params.user} prop='readers' /> </> - ); + ) } /** @@ -40,7 +40,7 @@ export function Readers() { * @param {{uname: string, prop: string}} props */ function Users({ uname, prop }) { - const [user, setUser] = useState({ uid: 0, uname: uname }); + const [user, setUser] = useState({ uid: 0, uname: uname }) return ( <UserInfo uname={uname} onUpdate={setUser}> <div style={{ display: 'flex', flexWrap: 'wrap', flexDirection: 'row' }}> @@ -52,5 +52,5 @@ function Users({ uname, prop }) { } </div> </UserInfo> - ); + ) } diff --git a/vnext/src/ui/VisitorContext.js b/vnext/src/ui/VisitorContext.js index 240b709b..9740f9ca 100644 --- a/vnext/src/ui/VisitorContext.js +++ b/vnext/src/ui/VisitorContext.js @@ -1,18 +1,18 @@ -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useState } from 'react' -const Visitor = createContext(); +const Visitor = createContext() /** @type {import('../api').SecureUser} */ const unknownUser = { uid: -1 -}; +} /** * @param { import('react').PropsWithChildren<{}> } props */ export function VisitorProvider({ children }) { - const state = useState(unknownUser); - return <Visitor.Provider value={state}>{children}</Visitor.Provider>; + const state = useState(unknownUser) + return <Visitor.Provider value={state}>{children}</Visitor.Provider> } /** @@ -23,9 +23,9 @@ export function VisitorProvider({ children }) { * ]} visitor hook */ export function useVisitor() { - const visitor = useContext(Visitor); + const visitor = useContext(Visitor) if (visitor === undefined) { - throw new Error('useVisitor must be used within a VisitorProvider'); + throw new Error('useVisitor must be used within a VisitorProvider') } - return visitor; + return visitor }
\ No newline at end of file diff --git a/vnext/src/ui/__tests__/Avatar.test.js b/vnext/src/ui/__tests__/Avatar.test.js index f454f6c7..7aea804c 100644 --- a/vnext/src/ui/__tests__/Avatar.test.js +++ b/vnext/src/ui/__tests__/Avatar.test.js @@ -1,14 +1,14 @@ -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom' -import Avatar from '../Avatar'; -import renderer from 'react-test-renderer'; +import Avatar from '../Avatar' +import renderer from 'react-test-renderer' test('Avatar renders correctly', () => { const component = renderer.create( <MemoryRouter> <Avatar user={{ uid: 1, uname: 'ugnich', avatar: 'https://juick.com/i/a/1-deadbeef.png' }} /> </MemoryRouter> - ); - let tree = component.toJSON(); - expect(tree).toMatchSnapshot(); -}); + ) + let tree = component.toJSON() + expect(tree).toMatchSnapshot() +}) diff --git a/vnext/src/ui/__tests__/MessageInput.test.js b/vnext/src/ui/__tests__/MessageInput.test.js index 0bfe2569..4af36b71 100644 --- a/vnext/src/ui/__tests__/MessageInput.test.js +++ b/vnext/src/ui/__tests__/MessageInput.test.js @@ -1,46 +1,46 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react' -import MessageInput from '../MessageInput'; +import MessageInput from '../MessageInput' test('Gives immediate focus on to textarea on load', async () => { - let draft = 'draft'; - render(<MessageInput text={draft} onSend={async () => { return true; }} />); - expect(screen.getByText(draft)).toHaveFocus(); -}); + let draft = 'draft' + render(<MessageInput text={draft} onSend={async () => { return true }} />) + expect(screen.getByText(draft)).toHaveFocus() +}) test('Submits on ctrl-enter and pass validation', async () => { - let result = false; - const onSend = jest.fn(async ({ body }) => { result = body === 'YO'; return result; }); - let draft = 'draft'; - render(<MessageInput onSend={onSend} text={draft} />); - let textarea = screen.getByText(draft); - fireEvent.change(textarea, { target: { value: 'HI' } }); + let result = false + const onSend = jest.fn(async ({ body }) => { result = body === 'YO'; return result }) + let draft = 'draft' + render(<MessageInput onSend={onSend} text={draft} />) + let textarea = screen.getByText(draft) + fireEvent.change(textarea, { target: { value: 'HI' } }) // this event should not submit fireEvent.keyPress(textarea, { charCode: 13, which: 13, keyCode: 13 - }); + }) // this event should submit fireEvent.keyPress(textarea, { charCode: 13, which: 13, keyCode: 13, ctrlKey: true - }); - expect(onSend).toHaveBeenCalledTimes(1); - expect(result).toBe(false); - fireEvent.change(textarea, { target: { value: 'YO' } }); + }) + expect(onSend).toHaveBeenCalledTimes(1) + expect(result).toBe(false) + fireEvent.change(textarea, { target: { value: 'YO' } }) fireEvent.keyPress(textarea, { charCode: 13, which: 13, keyCode: 13, ctrlKey: true - }); - expect(onSend).toHaveBeenCalledTimes(2); - expect(result).toBe(true); - await waitFor(() => expect(textarea).toHaveTextContent('')); - textarea.focus(); - expect(textarea).toHaveFocus(); -}); + }) + expect(onSend).toHaveBeenCalledTimes(2) + expect(result).toBe(true) + await waitFor(() => expect(textarea).toHaveTextContent('')) + textarea.focus() + expect(textarea).toHaveFocus() +}) diff --git a/vnext/src/ui/__tests__/UserLink.test.js b/vnext/src/ui/__tests__/UserLink.test.js index 6bb4da29..99ca42ce 100644 --- a/vnext/src/ui/__tests__/UserLink.test.js +++ b/vnext/src/ui/__tests__/UserLink.test.js @@ -1,10 +1,10 @@ -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom' -import { UserLink } from '../UserInfo'; -import renderer, { act } from 'react-test-renderer'; +import { UserLink } from '../UserInfo' +import renderer, { act } from 'react-test-renderer' test('UserLink renders correctly', async () => { - let component = null; + let component = null act(() => { component = renderer.create( <MemoryRouter> @@ -14,8 +14,8 @@ test('UserLink renders correctly', async () => { <UserLink user={{ uid: 0, uname: '', uri: 'https://example.com/u/test' }} /> </> </MemoryRouter> - ); - }); - let tree = component.toJSON(); - expect(tree).toMatchSnapshot(); -}); + ) + }) + let tree = component.toJSON() + expect(tree).toMatchSnapshot() +}) diff --git a/vnext/src/ui/helpers/BubbleStyle.js b/vnext/src/ui/helpers/BubbleStyle.js index d2886e1e..def60b62 100644 --- a/vnext/src/ui/helpers/BubbleStyle.js +++ b/vnext/src/ui/helpers/BubbleStyle.js @@ -4,9 +4,9 @@ * @returns { import('react').CSSProperties} CSS properties */ export function chatItemStyle(me, msg) { - const user = msg.user; - const isMe = me.uid === user.uid; - const alignment = isMe ? 'flex-end' : 'flex-start'; + const user = msg.user + const isMe = me.uid === user.uid + const alignment = isMe ? 'flex-end' : 'flex-start' return { padding: '3px 6px', listStyle: 'none', @@ -14,5 +14,5 @@ export function chatItemStyle(me, msg) { display: 'flex', flexDirection: 'column', alignItems: alignment - }; + } } |