diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 68ea04de8c0a14621ec980cf503c6d09e04a9038..0000000000000000000000000000000000000000 --- a/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -tabWidth: 4 \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000000000000000000000000000000000000..ae7b27fbfa2fd96a0c3b4dd4fe2a529452e1c4ff --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,9 @@ +module.exports = { + "tabWidth": 4, + "useTabs": true, + "trailingComma": "all", + "jsxBracketSameLine": true, + "importOrder": ["/(lib)", "/(redux)", "/(context)", "/(ui|common)|.svg$", "^[./]"], + "importOrderSeparation": true, +} + \ No newline at end of file diff --git a/package.json b/package.json index ec6f6d2d21dace77f9b543710b27198c256745fd..2e298ff3fcfa9c664948d9fce65a63aee4f166b5 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@tippyjs/react": "^4.2.5", "@traptitech/markdown-it-katex": "^3.4.3", "@traptitech/markdown-it-spoiler": "^1.1.6", + "@trivago/prettier-plugin-sort-imports": "^2.0.2", "@types/lodash.defaultsdeep": "^4.6.6", "@types/lodash.isequal": "^4.5.5", "@types/markdown-it": "^12.0.2", diff --git a/src/components/common/LocaleSelector.tsx b/src/components/common/LocaleSelector.tsx index 225e2c389626e5dfb7ff41a00bd87e86093444e2..7f91b1c7a3063eec5f63dd373c797dee3819af81 100644 --- a/src/components/common/LocaleSelector.tsx +++ b/src/components/common/LocaleSelector.tsx @@ -1,9 +1,9 @@ import ComboBox from "../ui/ComboBox"; +import { dispatch } from "../../redux"; import { connectState } from "../../redux/connector"; -import { WithDispatcher } from "../../redux/reducers"; import { Language, LanguageEntry, Languages } from "../../context/Locale"; -type Props = WithDispatcher & { +type Props = { locale: string; }; @@ -12,8 +12,7 @@ export function LocaleSelector(props: Props) { <ComboBox value={props.locale} onChange={e => - props.dispatcher && - props.dispatcher({ + dispatch({ type: "SET_LOCALE", locale: e.currentTarget.value as Language }) @@ -37,6 +36,5 @@ export default connectState( return { locale: state.locale }; - }, - true + } ); diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index b035bfa627a2af9d4a317bd4e63e3b3b06641340..486f6f28a8da03f0477f3477cc7b95764aae47ff 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -1,11 +1,11 @@ import { ulid } from "ulid"; import { Text } from "preact-i18n"; -import Tooltip, { PermissionTooltip } from "../Tooltip"; import { Channel } from "revolt.js"; import styled from "styled-components"; +import { dispatch } from "../../../redux"; import { defer } from "../../../lib/defer"; import IconButton from "../../ui/IconButton"; -import { X } from '@styled-icons/boxicons-regular'; +import { PermissionTooltip } from "../Tooltip"; import { Send } from '@styled-icons/boxicons-solid'; import { debounce } from "../../../lib/debounce"; import Axios, { CancelTokenSource } from "axios"; @@ -13,7 +13,6 @@ import { useTranslation } from "../../../lib/i18n"; import { Reply } from "../../../redux/reducers/queue"; import { connectState } from "../../../redux/connector"; import { SoundContext } from "../../../context/Settings"; -import { WithDispatcher } from "../../../redux/reducers"; import { takeError } from "../../../context/revoltjs/util"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import AutoComplete, { useAutoComplete } from "../AutoComplete"; @@ -30,9 +29,8 @@ import { ShieldX } from "@styled-icons/boxicons-regular"; import ReplyBar from "./bars/ReplyBar"; import FilePreview from './bars/FilePreview'; -import { Styleshare } from "@styled-icons/simple-icons"; -type Props = WithDispatcher & { +type Props = { channel: Channel; draft?: string; }; @@ -77,7 +75,7 @@ const Action = styled.div` // ! FIXME: add to app config and load from app config export const CAN_UPLOAD_AT_ONCE = 5; -function MessageBox({ channel, draft, dispatcher }: Props) { +function MessageBox({ channel, draft }: Props) { const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' }); const [ typing, setTyping ] = useState<boolean | number>(false); const [ replies, setReplies ] = useState<Reply[]>([]); @@ -102,13 +100,13 @@ function MessageBox({ channel, draft, dispatcher }: Props) { function setMessage(content?: string) { if (content) { - dispatcher({ + dispatch({ type: "SET_DRAFT", channel: channel._id, content }); } else { - dispatcher({ + dispatch({ type: "CLEAR_DRAFT", channel: channel._id }); @@ -148,7 +146,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) { playSound('outbound'); const nonce = ulid(); - dispatcher({ + dispatch({ type: "QUEUE_ADD", nonce, channel: channel._id, @@ -171,7 +169,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) { replies }); } catch (error) { - dispatcher({ + dispatch({ type: "QUEUE_FAIL", error: takeError(error), nonce @@ -383,7 +381,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) { ) } -export default connectState<Omit<Props, "dispatcher" | "draft">>(MessageBox, (state, { channel }) => { +export default connectState<Omit<Props, "dispatch" | "draft">>(MessageBox, (state, { channel }) => { return { draft: state.drafts[channel._id] } diff --git a/src/components/common/messaging/attachments/Attachment.module.scss b/src/components/common/messaging/attachments/Attachment.module.scss index 5aa89744d83a997be557862d27105019ed2cfaf2..8b5529d41e8eab28e646c2a06f5fb6bf0d36e4e0 100644 --- a/src/components/common/messaging/attachments/Attachment.module.scss +++ b/src/components/common/messaging/attachments/Attachment.module.scss @@ -1,13 +1,12 @@ .attachment { - border-radius: 6px; - margin: .125rem 0 .125rem; + display: grid; + grid-auto-columns: min(100%, 480px); + grid-auto-flow: row dense; - height: auto; + width: max-content; - max-height: 640px; - max-width: min(480px, 100%); - - object-fit: contain; + border-radius: 6px; + margin: .125rem 0 .125rem; &[data-spoiler="true"] { filter: blur(30px); @@ -20,6 +19,16 @@ &.image { cursor: pointer; + + max-height: 640px; + max-width: min(480px, 100%); + + object-fit: contain; + + &.loaded { + width: auto; + height: auto; + } } &.video { @@ -29,8 +38,15 @@ } video { - width: 100%; border-radius: 0 0 6px 6px; + + max-height: 640px; + max-width: min(480px, 100%); + } + + video.loaded { + width: auto; + height: auto; } } @@ -59,11 +75,12 @@ } &.text { - display: flex; - overflow: hidden; + width: 100%; max-width: 800px; + overflow: hidden; + grid-auto-columns: unset; + border-radius: 6px; - flex-direction: column; .textContent { height: 140px; @@ -92,35 +109,48 @@ } } +.actions.imageAction { + grid-template: + "name icon download" auto + "size icon download" auto + / minmax(20px, 1fr) min-content min-content; +} + .actions { - gap: 8px; + display: grid; + grid-template: + "icon name download" auto + "icon size download" auto + / min-content minmax(20px, 1fr) min-content; + + align-items: center; + column-gap: 8px; + + width: 100%; padding: 8px; - display: flex; overflow: none; - max-width: 100%; - align-items: center; - flex-direction: row; + color: var(--foreground); background: var(--secondary-background); - > svg { - flex-shrink: 0; + span { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } - .info { - display: flex; - flex-direction: column; - flex-grow: 1; + .filesize { + grid-area: size; - > span { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } + font-size: 10px; + color: var(--secondary-foreground); + } - .filesize { - font-size: 10px; - color: var(--secondary-foreground); - } + .downloadIcon { + grid-area: download; + } + + .iconType { + grid-area: icon; } } diff --git a/src/components/common/messaging/attachments/Attachment.tsx b/src/components/common/messaging/attachments/Attachment.tsx index 15c4253658859c3a910e4c3407385be13e8a7036..2ef979a2c8c22b29d0fbbd835cb3aad8357f44df 100644 --- a/src/components/common/messaging/attachments/Attachment.tsx +++ b/src/components/common/messaging/attachments/Attachment.tsx @@ -21,6 +21,7 @@ export default function Attachment({ attachment, hasContent }: Props) { const { openScreen } = useIntermediate(); const { filename, metadata } = attachment; const [ spoiler, setSpoiler ] = useState(filename.startsWith("SPOILER_")); + const [ loaded, setLoaded ] = useState(false) const url = client.generateFileURL(attachment, { width: MAX_ATTACHMENT_WIDTH * 1.5 }, true); @@ -44,7 +45,7 @@ export default function Attachment({ attachment, hasContent }: Props) { height={metadata.height} data-spoiler={spoiler} data-has-content={hasContent} - className={classNames(styles.attachment, styles.image)} + className={classNames(styles.attachment, styles.image, loaded && styles.loaded)} onClick={() => openScreen({ id: "image_viewer", attachment }) } @@ -52,6 +53,7 @@ export default function Attachment({ attachment, hasContent }: Props) { ev.button === 1 && window.open(url, "_blank") } + onLoad={() => setLoaded(true)} /> </div> ); @@ -85,11 +87,15 @@ export default function Attachment({ attachment, hasContent }: Props) { <AttachmentActions attachment={attachment} /> <video src={url} + width={metadata.width} + height={metadata.height} + className={classNames(loaded && styles.loaded)} controls onMouseDown={ev => ev.button === 1 && window.open(url, "_blank") } + onLoadedMetadata={() => setLoaded(true)} /> </div> </div> diff --git a/src/components/common/messaging/attachments/AttachmentActions.tsx b/src/components/common/messaging/attachments/AttachmentActions.tsx index 0248abd455f59bc78757cb4ba2501c85a21d491e..8b297f4becb000814764698ea314b911d53e7186 100644 --- a/src/components/common/messaging/attachments/AttachmentActions.tsx +++ b/src/components/common/messaging/attachments/AttachmentActions.tsx @@ -5,6 +5,7 @@ import { Attachment } from "revolt.js/dist/api/objects"; import { determineFileSize } from '../../../../lib/fileSize'; import { AppContext } from '../../../../context/revoltjs/RevoltClient'; import { Download, LinkExternal, File, Headphone, Video } from '@styled-icons/boxicons-regular'; +import classNames from 'classnames'; interface Props { attachment: Attachment; @@ -24,17 +25,15 @@ export default function AttachmentActions({ attachment }: Props) { switch (metadata.type) { case 'Image': return ( - <div className={styles.actions}> - <div className={styles.info}> - <span className={styles.filename}>{filename}</span> - <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span> - </div> - <a href={open_url} target="_blank"> + <div className={classNames(styles.actions, styles.imageAction)}> + <span className={styles.filename}>{filename}</span> + <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span> + <a href={open_url} target="_blank" className={styles.iconType} > <IconButton> <LinkExternal size={24} /> </IconButton> </a> - <a href={download_url} download target="_blank"> + <a href={download_url} className={styles.downloadIcon} download target="_blank"> <IconButton> <Download size={24} /> </IconButton> @@ -43,13 +42,11 @@ export default function AttachmentActions({ attachment }: Props) { ) case 'Audio': return ( - <div className={styles.actions}> - <Headphone size={24} /> - <div className={styles.info}> - <span className={styles.filename}>{filename}</span> - <span className={styles.filesize}>{filesize}</span> - </div> - <a href={download_url} download target="_blank"> + <div className={classNames(styles.actions, styles.audioAction)}> + <Headphone size={24} className={styles.iconType} /> + <span className={styles.filename}>{filename}</span> + <span className={styles.filesize}>{filesize}</span> + <a href={download_url} className={styles.downloadIcon} download target="_blank"> <IconButton> <Download size={24} /> </IconButton> @@ -58,13 +55,11 @@ export default function AttachmentActions({ attachment }: Props) { ) case 'Video': return ( - <div className={styles.actions}> - <Video size={24} /> - <div className={styles.info}> - <span className={styles.filename}>{filename}</span> - <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span> - </div> - <a href={download_url} download target="_blank"> + <div className={classNames(styles.actions, styles.videoAction)}> + <Video size={24} className={styles.iconType} /> + <span className={styles.filename}>{filename}</span> + <span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span> + <a href={download_url} className={styles.downloadIcon} download target="_blank"> <IconButton> <Download size={24} /> </IconButton> @@ -74,12 +69,10 @@ export default function AttachmentActions({ attachment }: Props) { default: return ( <div className={styles.actions}> - <File size={24} /> - <div className={styles.info}> - <span className={styles.filename}>{filename}</span> - <span className={styles.filesize}>{filesize}</span> - </div> - <a href={download_url} download target="_blank"> + <File size={24} className={styles.iconType} /> + <span className={styles.filename}>{filename}</span> + <span className={styles.filesize}>{filesize}</span> + <a href={download_url} className={styles.downloadIcon} download target="_blank"> <IconButton> <Download size={24} /> </IconButton> diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index 16e586bbcfe6f93e451479596ac3a81faad160e0..fc883f42aa86667b020c4b2f52c3d3a2cb5aa45f 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -3,12 +3,12 @@ import { useContext, useEffect } from "preact/hooks"; import { Home, UserDetail, Wrench, Notepad } from "@styled-icons/boxicons-solid"; import Category from '../../ui/Category'; +import { dispatch } from "../../../redux"; import PaintCounter from "../../../lib/PaintCounter"; import UserHeader from "../../common/user/UserHeader"; import { Channels } from "revolt.js/dist/api/objects"; import { connectState } from "../../../redux/connector"; import ConnectionStatus from '../items/ConnectionStatus'; -import { WithDispatcher } from "../../../redux/reducers"; import { Unreads } from "../../../redux/reducers/unreads"; import ConditionalLink from "../../../lib/ConditionalLink"; import { mapChannelWithUnread, useUnreads } from "./common"; @@ -23,7 +23,7 @@ import { useDMs, useForceUpdate, useUsers } from "../../../context/revoltjs/hook import placeholderSVG from "../items/placeholder.svg"; -type Props = WithDispatcher & { +type Props = { unreads: Unreads; } @@ -43,7 +43,7 @@ function HomeSidebar(props: Props) { useEffect(() => { if (!channel) return; - props.dispatcher({ + dispatch({ type: 'LAST_OPENED_SET', parent: 'home', child: channel @@ -148,6 +148,5 @@ export default connectState( unreads: state.unreads }; }, - true, true ); diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index 3dd7aff9d37dddba1ef063fa6414b0978d4d8e27..280dd8295c9a89677a58fde2c5740367d157d35f 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -2,7 +2,6 @@ import { Redirect, useParams } from "react-router"; import { ChannelButton } from "../items/ButtonItem"; import { Channels } from "revolt.js/dist/api/objects"; import { Unreads } from "../../../redux/reducers/unreads"; -import { WithDispatcher } from "../../../redux/reducers"; import { useChannels, useForceUpdate, useServer } from "../../../context/revoltjs/hooks"; import { mapChannelWithUnread, useUnreads } from "./common"; import ConnectionStatus from '../items/ConnectionStatus'; @@ -13,6 +12,7 @@ import { attachContextMenu } from 'preact-context-menu'; import ServerHeader from "../../common/ServerHeader"; import { useEffect } from "preact/hooks"; import Category from "../../ui/Category"; +import { dispatch } from "../../../redux"; import ConditionalLink from "../../../lib/ConditionalLink"; import CollapsibleSection from "../../common/CollapsibleSection"; @@ -43,7 +43,7 @@ const ServerList = styled.div` } `; -function ServerSidebar(props: Props & WithDispatcher) { +function ServerSidebar(props: Props) { const { server: server_id, channel: channel_id } = useParams<{ server?: string, channel?: string }>(); const ctx = useForceUpdate(); @@ -61,7 +61,7 @@ function ServerSidebar(props: Props & WithDispatcher) { useEffect(() => { if (!channel_id) return; - props.dispatcher({ + dispatch({ type: 'LAST_OPENED_SET', parent: server_id!, child: channel_id! @@ -130,6 +130,5 @@ export default connectState( return { unreads: state.unreads }; - }, - true + } ); diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts index 2a681d10d2f025881182ebd31a29aa2ca9fcfdb7..f7ca164fbc47e1f2489952098294ec05366d894c 100644 --- a/src/components/navigation/left/common.ts +++ b/src/components/navigation/left/common.ts @@ -1,15 +1,15 @@ import { Channel } from "revolt.js"; +import { dispatch } from "../../../redux"; import { useLayoutEffect } from "preact/hooks"; -import { WithDispatcher } from "../../../redux/reducers"; import { Unreads } from "../../../redux/reducers/unreads"; import { HookContext, useForceUpdate } from "../../../context/revoltjs/hooks"; -type UnreadProps = WithDispatcher & { +type UnreadProps = { channel: Channel; unreads: Unreads; } -export function useUnreads({ channel, unreads, dispatcher }: UnreadProps, context?: HookContext) { +export function useUnreads({ channel, unreads }: UnreadProps, context?: HookContext) { const ctx = useForceUpdate(context); useLayoutEffect(() => { @@ -23,7 +23,7 @@ export function useUnreads({ channel, unreads, dispatcher }: UnreadProps, contex if (target.last_message) { const message = typeof target.last_message === 'string' ? target.last_message : target.last_message._id; if (!unread || (unread && message.localeCompare(unread) > 0)) { - dispatcher({ + dispatch({ type: "UNREADS_MARK_READ", channel: channel._id, message diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index 03287b15375b39de5919faf944c1111308b7e25b..40be8041dd0fcae9d11a6313c9a0a680bdadff88 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -2,7 +2,6 @@ import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; import { User } from "revolt.js"; -import Details from "../../../components/ui/Details"; import Category from "../../ui/Category"; import { useParams } from "react-router"; import { UserButton } from "../items/ButtonItem"; @@ -15,6 +14,7 @@ import { AppContext, ClientStatus, StatusContext } from "../../../context/revolt import { HookContext, useChannel, useForceUpdate, useUsers } from "../../../context/revoltjs/hooks"; import placeholderSVG from "../items/placeholder.svg"; +import Preloader from "../../ui/Preloader"; interface Props { ctx: HookContext @@ -126,6 +126,7 @@ export function GroupMemberSidebar({ channel, ctx }: Props & { channel: Channels export function ServerMemberSidebar({ channel, ctx }: Props & { channel: Channels.TextChannel }) { const [members, setMembers] = useState<Servers.Member[] | undefined>(undefined); const users = useUsers(members?.map(x => x._id.user) ?? []).filter(x => typeof x !== 'undefined', ctx) as Users.User[]; + const { openScreen } = useIntermediate(); const status = useContext(StatusContext); const client = useContext(AppContext); @@ -180,17 +181,16 @@ export function ServerMemberSidebar({ channel, ctx }: Props & { channel: Channel </span> } /> - {users.length === 0 && <img src={placeholderSVG} />} + {!members && <Preloader type="ring" />} + {members && users.length === 0 && <img src={placeholderSVG} />} {users.map( user => user && ( - // <LinkProfile user_id={user._id}> - <UserButton - key={user._id} - user={user} - context={channel} - /> - // </LinkProfile> + <UserButton + key={user._id} + user={user} + context={channel} + onClick={() => openScreen({ id: 'profile', user_id: user._id })} /> ) )} </GenericSidebarList> diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx index 065d6952b12c40a3f557479ba3c8badab5ce63e1..8a9fc54e11c32954e398f3ab4826726eb64376ef 100644 --- a/src/context/Theme.tsx +++ b/src/context/Theme.tsx @@ -195,6 +195,9 @@ export const MONOSCAPE_FONTS: Record<MonoscapeFonts, { name: string, load: () => export const FONT_KEYS = Object.keys(FONTS).sort(); export const MONOSCAPE_FONT_KEYS = Object.keys(MONOSCAPE_FONTS).sort(); +export const DEFAULT_FONT = 'Open Sans'; +export const DEFAULT_MONO_FONT = 'Fira Code'; + // Generated from https://gitlab.insrt.uk/revolt/community/themes export const PRESETS: Record<string, Theme> = { light: { @@ -281,13 +284,13 @@ function Theme({ children, options }: Props) { const root = document.documentElement.style; useEffect(() => { - const font = theme.font ?? 'Inter'; + const font = theme.font ?? DEFAULT_FONT; root.setProperty('--font', `"${font}"`); FONTS[font].load(); }, [ theme.font ]); useEffect(() => { - const font = theme.monoscapeFont ?? 'Fira Code'; + const font = theme.monoscapeFont ?? DEFAULT_MONO_FONT; root.setProperty('--monoscape-font', `"${font}"`); MONOSCAPE_FONTS[font].load(); }, [ theme.monoscapeFont ]); diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index a975c101e85dffbde75601c92f0bbb8455ff7fde..91dc602f0bdc2724b4b55fdf271b6a4fe4edc0d4 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -2,12 +2,12 @@ import { openDB } from 'idb'; import { Client } from "revolt.js"; import { takeError } from "./util"; import { createContext } from "preact"; +import { dispatch } from '../../redux'; import { Children } from "../../types/Preact"; import { useHistory } from 'react-router-dom'; import { Route } from "revolt.js/dist/api/routes"; import { connectState } from "../../redux/connector"; import Preloader from "../../components/ui/Preloader"; -import { WithDispatcher } from "../../redux/reducers"; import { AuthState } from "../../redux/reducers/auth"; import { useEffect, useMemo, useState } from "preact/hooks"; import { useIntermediate } from '../intermediate/Intermediate'; @@ -41,12 +41,12 @@ export const AppContext = createContext<Client>(null!); export const StatusContext = createContext<ClientStatus>(null!); export const OperationsContext = createContext<ClientOperations>(null!); -type Props = WithDispatcher & { +type Props = { auth: AuthState; children: Children; }; -function Context({ auth, children, dispatcher }: Props) { +function Context({ auth, children }: Props) { const history = useHistory(); const { openScreen } = useIntermediate(); const [status, setStatus] = useState(ClientStatus.INIT); @@ -94,7 +94,7 @@ function Context({ auth, children, dispatcher }: Props) { const onboarding = await client.login(data); setReconnectDisallowed(false); const login = () => - dispatcher({ + dispatch({ type: "LOGIN", session: client.session! // This [null assertion] is ok, we should have a session by now. - insert's words }); @@ -114,10 +114,10 @@ function Context({ auth, children, dispatcher }: Props) { } }, logout: async shouldRequest => { - dispatcher({ type: "LOGOUT" }); + dispatch({ type: "LOGOUT" }); client.reset(); - dispatcher({ type: "RESET" }); + dispatch({ type: "RESET" }); openScreen({ id: "none" }); setStatus(ClientStatus.READY); @@ -145,7 +145,7 @@ function Context({ auth, children, dispatcher }: Props) { } }, [ client, auth.active ]); - useEffect(() => registerEvents({ operations, dispatcher }, setStatus, client), [ client ]); + useEffect(() => registerEvents({ operations }, setStatus, client), [ client ]); useEffect(() => { (async () => { @@ -154,7 +154,7 @@ function Context({ auth, children, dispatcher }: Props) { } if (auth.active) { - dispatcher({ type: "QUEUE_FAIL_ALL" }); + dispatch({ type: "QUEUE_FAIL_ALL" }); const active = auth.accounts[auth.active]; client.user = client.users.get(active.session.user_id); @@ -226,6 +226,5 @@ export default connectState<{ children: Children }>( auth: state.auth, sync: state.sync }; - }, - true + } ); diff --git a/src/context/revoltjs/StateMonitor.tsx b/src/context/revoltjs/StateMonitor.tsx index e333c7ac5cf942acad6d8018cbbf3eb917f54fb6..cc0d7616196af52da5a2233eb48ddb8400c96164 100644 --- a/src/context/revoltjs/StateMonitor.tsx +++ b/src/context/revoltjs/StateMonitor.tsx @@ -7,10 +7,10 @@ import { AppContext } from "./RevoltClient"; import { Typing } from "../../redux/reducers/typing"; import { useContext, useEffect } from "preact/hooks"; import { connectState } from "../../redux/connector"; -import { WithDispatcher } from "../../redux/reducers"; import { QueuedMessage } from "../../redux/reducers/queue"; +import { dispatch } from "../../redux"; -type Props = WithDispatcher & { +type Props = { messages: QueuedMessage[]; typing: Typing }; @@ -19,7 +19,7 @@ function StateMonitor(props: Props) { const client = useContext(AppContext); useEffect(() => { - props.dispatcher({ + dispatch({ type: 'QUEUE_DROP_ALL' }); }, [ ]); @@ -29,7 +29,7 @@ function StateMonitor(props: Props) { if (!msg.nonce) return; if (!props.messages.find(x => x.id === msg.nonce)) return; - props.dispatcher({ + dispatch({ type: 'QUEUE_REMOVE', nonce: msg.nonce }); @@ -47,7 +47,7 @@ function StateMonitor(props: Props) { for (let user of users) { if (+ new Date() > user.started + 5000) { - props.dispatcher({ + dispatch({ type: 'TYPING_STOP', channel, user: user.id @@ -73,6 +73,5 @@ export default connectState( messages: [...state.queue], typing: state.typing }; - }, - true + } ); diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx index 2c1bbd83172f847ded161e46af4c2fea4bf039ed..f09ffb0d1ca7477e4c715085267f541b85f680e5 100644 --- a/src/context/revoltjs/SyncManager.tsx +++ b/src/context/revoltjs/SyncManager.tsx @@ -7,14 +7,14 @@ import { Language } from "../Locale"; import { Sync } from "revolt.js/dist/api/objects"; import { useContext, useEffect } from "preact/hooks"; import { connectState } from "../../redux/connector"; -import { WithDispatcher } from "../../redux/reducers"; import { Settings } from "../../redux/reducers/settings"; import { Notifications } from "../../redux/reducers/notifications"; import { AppContext, ClientStatus, StatusContext } from "./RevoltClient"; import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"; import { DEFAULT_ENABLED_SYNC, SyncData, SyncKeys, SyncOptions } from "../../redux/reducers/sync"; +import { dispatch } from "../../redux"; -type Props = WithDispatcher & { +type Props = { settings: Settings, locale: Language, sync: SyncOptions, @@ -54,7 +54,7 @@ function SyncManager(props: Props) { client .syncFetchSettings(DEFAULT_ENABLED_SYNC.filter(x => !props.sync?.disabled?.includes(x))) .then(data => { - props.dispatcher({ + dispatch({ type: 'SYNC_UPDATE', update: mapSync(data) }); @@ -62,13 +62,13 @@ function SyncManager(props: Props) { client .syncFetchUnreads() - .then(unreads => props.dispatcher({ type: 'UNREADS_SET', unreads })); + .then(unreads => dispatch({ type: 'UNREADS_SET', unreads })); } }, [ status ]); function syncChange(key: SyncKeys, data: any) { let timestamp = + new Date(); - props.dispatcher({ + dispatch({ type: 'SYNC_SET_REVISION', key, timestamp @@ -99,7 +99,7 @@ function SyncManager(props: Props) { if (packet.type === 'UserSettingsUpdate') { let update: { [key in SyncKeys]?: [ number, SyncData[key] ] } = mapSync(packet.update, props.sync.revision); - props.dispatcher({ + dispatch({ type: 'SYNC_UPDATE', update }); @@ -122,6 +122,5 @@ export default connectState( sync: state.sync, notifications: state.notifications }; - }, - true + } ); diff --git a/src/context/revoltjs/events.ts b/src/context/revoltjs/events.ts index 11732da5386353a119cd3c82f2113396619e1f70..319894689955981271fff384005383b3855eee60 100644 --- a/src/context/revoltjs/events.ts +++ b/src/context/revoltjs/events.ts @@ -1,11 +1,11 @@ import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"; -import { WithDispatcher } from "../../redux/reducers"; import { Client, Message } from "revolt.js/dist"; import { ClientOperations, ClientStatus } from "./RevoltClient"; import { StateUpdater } from "preact/hooks"; +import { dispatch } from "../../redux"; export var preventReconnect = false; let preventUntil = 0; @@ -15,9 +15,8 @@ export function setReconnectDisallowed(allowed: boolean) { } export function registerEvents({ - operations, - dispatcher -}: { operations: ClientOperations } & WithDispatcher, setStatus: StateUpdater<ClientStatus>, client: Client) { + operations +}: { operations: ClientOperations }, setStatus: StateUpdater<ClientStatus>, client: Client) { function attemptReconnect() { if (preventReconnect) return; function reconnect() { @@ -47,7 +46,7 @@ export function registerEvents({ switch (packet.type) { case "ChannelStartTyping": { if (packet.user === client.user?._id) return; - dispatcher({ + dispatch({ type: "TYPING_START", channel: packet.id, user: packet.user @@ -56,7 +55,7 @@ export function registerEvents({ } case "ChannelStopTyping": { if (packet.user === client.user?._id) return; - dispatcher({ + dispatch({ type: "TYPING_STOP", channel: packet.id, user: packet.user @@ -64,7 +63,7 @@ export function registerEvents({ break; } case "ChannelAck": { - dispatcher({ + dispatch({ type: "UNREADS_MARK_READ", channel: packet.id, message: packet.message_id @@ -76,7 +75,7 @@ export function registerEvents({ message: (message: Message) => { if (message.mentions?.includes(client.user!._id)) { - dispatcher({ + dispatch({ type: "UNREADS_MENTION", channel: message.channel, message: message._id diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 4b7a868fbafddfe66549bf99bb29fe683aa64cda..89f1a6bb87f906c6cde55a35b4ab5b7590a13251 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -10,7 +10,6 @@ import { } from "preact-context-menu"; import { ChannelPermission, ServerPermission, UserPermission } from "revolt.js/dist/api/permissions"; import { QueuedMessage } from "../redux/reducers/queue"; -import { WithDispatcher } from "../redux/reducers"; import { useIntermediate } from "../context/intermediate/Intermediate"; import { AppContext, ClientStatus, StatusContext } from "../context/revoltjs/RevoltClient"; import { takeError } from "../context/revoltjs/util"; @@ -24,6 +23,7 @@ import { Cog } from "@styled-icons/boxicons-solid"; import { getNotificationState, Notifications, NotificationState } from "../redux/reducers/notifications"; import UserStatus from "../components/common/user/UserStatus"; import IconButton from "../components/ui/IconButton"; +import { dispatch } from "../redux"; interface ContextMenuData { user?: string; @@ -81,7 +81,7 @@ type Action = | { action: "open_server_channel_settings", server: string, id: string } | { action: "set_notification_state", key: string, state?: NotificationState }; -type Props = WithDispatcher & { +type Props = { notifications: Notifications }; @@ -110,7 +110,7 @@ function ContextMenus(props: Props) { data.channel.channel_type === 'VoiceChannel') return; let message = data.channel.channel_type === 'TextChannel' ? data.channel.last_message : data.channel.last_message._id; - props.dispatcher({ + dispatch({ type: "UNREADS_MARK_READ", channel: data.channel._id, message @@ -124,7 +124,7 @@ function ContextMenus(props: Props) { { const nonce = data.message.id; const fail = (error: any) => - props.dispatcher({ + dispatch({ type: "QUEUE_FAIL", nonce, error @@ -141,7 +141,7 @@ function ContextMenus(props: Props) { ) .catch(fail); - props.dispatcher({ + dispatch({ type: "QUEUE_START", nonce }); @@ -150,7 +150,7 @@ function ContextMenus(props: Props) { case "cancel_message": { - props.dispatcher({ + dispatch({ type: "QUEUE_REMOVE", nonce: data.message.id }); @@ -330,9 +330,9 @@ function ContextMenus(props: Props) { case "set_notification_state": { const { key, state } = data; if (state) { - props.dispatcher({ type: "NOTIFICATIONS_SET", key, state }); + dispatch({ type: "NOTIFICATIONS_SET", key, state }); } else { - props.dispatcher({ type: "NOTIFICATIONS_REMOVE", key }); + dispatch({ type: "NOTIFICATIONS_REMOVE", key }); } break; } @@ -760,6 +760,5 @@ export default connectState( return { notifications: state.notifications }; - }, - true + } ); diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx index 737aae9fc7172cc91981f9a8e71cd71398b2fcdd..714e4086ec8a7bb04743dd6d2245236d5ee2886a 100644 --- a/src/pages/settings/panes/Appearance.tsx +++ b/src/pages/settings/panes/Appearance.tsx @@ -6,14 +6,13 @@ import Checkbox from "../../../components/ui/Checkbox"; import ComboBox from "../../../components/ui/ComboBox"; import InputBox from "../../../components/ui/InputBox"; import { connectState } from "../../../redux/connector"; -import { WithDispatcher } from "../../../redux/reducers"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import ColourSwatches from "../../../components/ui/ColourSwatches"; import { EmojiPacks, Settings } from "../../../redux/reducers/settings"; import { useCallback, useContext, useEffect, useState } from "preact/hooks"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import CollapsibleSection from "../../../components/common/CollapsibleSection"; -import { FONTS, FONT_KEYS, MONOSCAPE_FONTS, MONOSCAPE_FONT_KEYS, Theme, ThemeContext, ThemeOptions } from "../../../context/Theme"; +import { DEFAULT_FONT, DEFAULT_MONO_FONT, FONTS, FONT_KEYS, MONOSCAPE_FONTS, MONOSCAPE_FONT_KEYS, Theme, ThemeContext, ThemeOptions } from "../../../context/Theme"; // @ts-ignore import pSBC from 'shade-blend-color'; @@ -25,25 +24,26 @@ import mutantSVG from '../assets/mutant_emoji.svg'; import notoSVG from '../assets/noto_emoji.svg'; import openmojiSVG from '../assets/openmoji_emoji.svg'; import twemojiSVG from '../assets/twemoji_emoji.svg'; +import { dispatch } from "../../../redux"; interface Props { settings: Settings; } // ! FIXME: code needs to be rewritten to fix jittering -export function Component(props: Props & WithDispatcher) { +export function Component(props: Props) { const theme = useContext(ThemeContext); const { writeClipboard, openScreen } = useIntermediate(); function setTheme(theme: ThemeOptions) { - props.dispatcher({ + dispatch({ type: "SETTINGS_SET_THEME", theme }); } function pushOverride(custom: Partial<Theme>) { - props.dispatcher({ + dispatch({ type: "SETTINGS_SET_THEME_OVERRIDE", custom }); @@ -58,7 +58,7 @@ export function Component(props: Props & WithDispatcher) { const emojiPack = props.settings.appearance?.emojiPack ?? 'mutant'; function setEmojiPack(emojiPack: EmojiPacks) { - props.dispatcher({ + dispatch({ type: 'SETTINGS_SET_APPEARANCE', options: { emojiPack @@ -135,7 +135,7 @@ export function Component(props: Props & WithDispatcher) { <h3> <Text id="app.settings.pages.appearance.font" /> </h3> - <ComboBox value={theme.font} onChange={e => setTheme({ custom: { font: e.currentTarget.value as any } })}> + <ComboBox value={theme.font ?? DEFAULT_FONT} onChange={e => setTheme({ custom: { font: e.currentTarget.value as any } })}> { FONT_KEYS .map(key => @@ -284,7 +284,7 @@ export function Component(props: Props & WithDispatcher) { <h3> <Text id="app.settings.pages.appearance.mono_font" /> </h3> - <ComboBox value={theme.monoscapeFont} onChange={e => setTheme({ custom: { monoscapeFont: e.currentTarget.value as any } })}> + <ComboBox value={theme.monoscapeFont ?? DEFAULT_MONO_FONT} onChange={e => setTheme({ custom: { monoscapeFont: e.currentTarget.value as any } })}> { MONOSCAPE_FONT_KEYS .map(key => @@ -313,6 +313,5 @@ export const Appearance = connectState( return { settings: state.settings }; - }, - true + } ); diff --git a/src/pages/settings/panes/Experiments.tsx b/src/pages/settings/panes/Experiments.tsx index 5d3d6ee95c9ca8cff1d66bb03ee953ecb6bd7ae0..c5dee4a9e303f48eda9334bd10aba88cfd3c6f0e 100644 --- a/src/pages/settings/panes/Experiments.tsx +++ b/src/pages/settings/panes/Experiments.tsx @@ -1,15 +1,15 @@ import { Text } from "preact-i18n"; import styles from "./Panes.module.scss"; +import { dispatch } from "../../../redux"; import Checkbox from "../../../components/ui/Checkbox"; import { connectState } from "../../../redux/connector"; -import { WithDispatcher } from "../../../redux/reducers"; import { AVAILABLE_EXPERIMENTS, ExperimentOptions } from "../../../redux/reducers/experiments"; interface Props { options?: ExperimentOptions; } -export function Component(props: Props & WithDispatcher) { +export function Component(props: Props) { return ( <div className={styles.experiments}> <h3> @@ -20,12 +20,12 @@ export function Component(props: Props & WithDispatcher) { key => <Checkbox checked={(props.options?.enabled ?? []).indexOf(key) > -1} - onChange={enabled => { - props.dispatcher({ + onChange={enabled => + dispatch({ type: enabled ? 'EXPERIMENTS_ENABLE' : 'EXPERIMENTS_DISABLE', key - }); - }} + }) + } > <Text id={`app.settings.pages.experiments.titles.${key}`} /> <p> @@ -51,6 +51,5 @@ export const ExperimentsPage = connectState( return { options: state.experiments }; - }, - true + } ); diff --git a/src/pages/settings/panes/Languages.tsx b/src/pages/settings/panes/Languages.tsx index e7ae6bf9f9b4fff785f09c69e80cd5454a70ab8a..077391a592367002bf611c480acb16ba71b08369 100644 --- a/src/pages/settings/panes/Languages.tsx +++ b/src/pages/settings/panes/Languages.tsx @@ -1,19 +1,19 @@ import { Text } from "preact-i18n"; import styles from "./Panes.module.scss"; +import { dispatch } from "../../../redux"; import Tip from "../../../components/ui/Tip"; import Emoji from "../../../components/common/Emoji"; import Checkbox from "../../../components/ui/Checkbox"; import { connectState } from "../../../redux/connector"; -import { WithDispatcher } from "../../../redux/reducers"; import { Language, LanguageEntry, Languages as Langs } from "../../../context/Locale"; -type Props = WithDispatcher & { +type Props = { locale: Language; } type Key = [ string, LanguageEntry ]; -function Entry({ entry: [ x, lang ], locale, dispatcher }: { entry: Key } & Props) { +function Entry({ entry: [ x, lang ], locale }: { entry: Key } & Props) { return ( <Checkbox key={x} @@ -21,7 +21,7 @@ function Entry({ entry: [ x, lang ], locale, dispatcher }: { entry: Key } & Prop checked={locale === x} onChange={v => { if (v) { - dispatcher({ + dispatch({ type: "SET_LOCALE", locale: x as Language }); @@ -80,6 +80,5 @@ export const Languages = connectState( return { locale: state.locale }; - }, - true + } ); diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx index eccd0cead1fef22f4779d8a945aa2817d52bae6f..5098c85e9266c9291ea5b9c981e0ce9757c32aee 100644 --- a/src/pages/settings/panes/Notifications.tsx +++ b/src/pages/settings/panes/Notifications.tsx @@ -1,9 +1,9 @@ import { Text } from "preact-i18n"; import styles from "./Panes.module.scss"; +import { dispatch } from "../../../redux"; import defaultsDeep from "lodash.defaultsdeep"; import Checkbox from "../../../components/ui/Checkbox"; import { connectState } from "../../../redux/connector"; -import { WithDispatcher } from "../../../redux/reducers"; import { SOUNDS_ARRAY } from "../../../assets/sounds/Audio"; import { useContext, useEffect, useState } from "preact/hooks"; import { urlBase64ToUint8Array } from "../../../lib/conversion"; @@ -15,7 +15,7 @@ interface Props { options?: NotificationOptions; } -export function Component({ options, dispatcher }: Props & WithDispatcher) { +export function Component({ options }: Props) { const client = useContext(AppContext); const { openScreen } = useIntermediate(); const [pushEnabled, setPushEnabled] = useState<undefined | boolean>( @@ -51,7 +51,7 @@ export function Component({ options, dispatcher }: Props & WithDispatcher) { } } - dispatcher({ + dispatch({ type: "SETTINGS_SET_NOTIFICATION_OPTIONS", options: { desktopEnabled } }); @@ -107,7 +107,7 @@ export function Component({ options, dispatcher }: Props & WithDispatcher) { <Checkbox checked={enabledSounds[key] ? true : false} onChange={enabled => - dispatcher({ + dispatch({ type: "SETTINGS_SET_NOTIFICATION_OPTIONS", options: { sounds: { @@ -131,6 +131,5 @@ export const Notifications = connectState( return { options: state.settings.notification }; - }, - true + } ); diff --git a/src/pages/settings/panes/Sync.tsx b/src/pages/settings/panes/Sync.tsx index 2055016b2c89dd208841822527e5e992db511fb5..20d51136818b1852067c9c0967bb77991bcceaef 100644 --- a/src/pages/settings/panes/Sync.tsx +++ b/src/pages/settings/panes/Sync.tsx @@ -1,15 +1,15 @@ import { Text } from "preact-i18n"; import styles from "./Panes.module.scss"; +import { dispatch } from "../../../redux"; import Checkbox from "../../../components/ui/Checkbox"; import { connectState } from "../../../redux/connector"; -import { WithDispatcher } from "../../../redux/reducers"; import { SyncKeys, SyncOptions } from "../../../redux/reducers/sync"; interface Props { options?: SyncOptions; } -export function Component(props: Props & WithDispatcher) { +export function Component(props: Props) { return ( <div className={styles.notifications}> <h3> @@ -26,12 +26,12 @@ export function Component(props: Props & WithDispatcher) { <Checkbox checked={(props.options?.disabled ?? []).indexOf(key) === -1} description={<Text id={`app.settings.pages.sync.descriptions.${key}`} />} - onChange={enabled => { - props.dispatcher({ + onChange={enabled => + dispatch({ type: enabled ? 'SYNC_ENABLE_KEY' : 'SYNC_DISABLE_KEY', key - }); - }} + }) + } > <Text id={`app.settings.pages.${title}`} /> </Checkbox> @@ -47,6 +47,5 @@ export const Sync = connectState( return { options: state.sync }; - }, - true + } ); diff --git a/src/redux/State.tsx b/src/redux/State.tsx index c7f8575df4170e1ea31aa8c9dcbce0286ad6b6a9..2b3576ff463f5aef876a5963c455bd5f94d17215 100644 --- a/src/redux/State.tsx +++ b/src/redux/State.tsx @@ -1,7 +1,7 @@ -import { store } from "."; import localForage from "localforage"; import { Provider } from "react-redux"; import { Children } from "../types/Preact"; +import { dispatch, State, store } from "."; import { useEffect, useState } from "preact/hooks"; interface Props { @@ -15,7 +15,7 @@ export default function State(props: Props) { localForage.getItem("state") .then(state => { if (state !== null) { - store.dispatch({ type: "__INIT", state }); + dispatch({ type: "__INIT", state: state as State }); } setLoaded(true); diff --git a/src/redux/connector.tsx b/src/redux/connector.tsx index 06ee1d91a6163a22c9947f43d5f6121065075ac3..6d9075cabcc0dd6891450442685edb1d5f3699c8 100644 --- a/src/redux/connector.tsx +++ b/src/redux/connector.tsx @@ -8,16 +8,8 @@ import { connect, ConnectedComponent } from "react-redux"; export function connectState<T>( component: (props: any) => h.JSX.Element | null, mapKeys: (state: State, props: T) => any, - useDispatcher?: boolean, memoize?: boolean ): ConnectedComponent<(props: any) => h.JSX.Element | null, T> { - let c = ( - useDispatcher - ? connect(mapKeys, (dispatcher) => { - return { dispatcher }; - }) - : connect(mapKeys) - )(component); - + let c = connect(mapKeys)(component); return memoize ? memo(c) : c; } diff --git a/src/redux/index.ts b/src/redux/index.ts index 4bee0dcd667006f406e247a19cb1e8d3c7f1ab35..e57d60ea1cc47d64e1b785bb60e060b73b20e389 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -1,6 +1,6 @@ import { createStore } from "redux"; -import rootReducer from "./reducers"; import localForage from "localforage"; +import rootReducer, { Action } from "./reducers"; import { Core } from "revolt.js/dist/api/objects"; import { Typing } from "./reducers/typing"; @@ -77,3 +77,7 @@ store.subscribe(() => { sectionToggle }); }); + +export function dispatch(action: Action) { + store.dispatch(action); +} diff --git a/src/redux/reducers/index.ts b/src/redux/reducers/index.ts index baf217dcf1dcc9c2252104e10f25309c5914b785..15b7e36294bb0e4a0e21592e5353ad1e241edd9a 100644 --- a/src/redux/reducers/index.ts +++ b/src/redux/reducers/index.ts @@ -47,8 +47,6 @@ export type Action = | SectionToggleAction | { type: "__INIT"; state: State }; -export type WithDispatcher = { dispatcher: (action: Action) => void }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any export function filter(obj: any, keys: string[]) { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/yarn.lock b/yarn.lock index 48f54160cb7eec8fdee682dcfe449bb9dd417660..6fec18642ef22fc373748e1f54afa9d80e866547 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,7 +9,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.14.5": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== @@ -21,6 +21,28 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.5.tgz#8ef4c18e58e801c5c95d3c1c0f2874a2680fadea" integrity sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w== +"@babel/core@7.13.10": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559" + integrity sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-compilation-targets" "^7.13.10" + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helpers" "^7.13.10" + "@babel/parser" "^7.13.10" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + lodash "^4.17.19" + semver "^6.3.0" + source-map "^0.5.0" + "@babel/core@^7.11.1", "@babel/core@^7.9.6": version "7.14.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab" @@ -42,7 +64,16 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.14.5": +"@babel/generator@7.13.9": + version "7.13.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" + integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== + dependencies: + "@babel/types" "^7.13.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.13.0", "@babel/generator@^7.13.9", "@babel/generator@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785" integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA== @@ -66,7 +97,7 @@ "@babel/helper-explode-assignable-expression" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.14.5": +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf" integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw== @@ -117,7 +148,7 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-function-name@^7.14.5": +"@babel/helper-function-name@^7.12.13", "@babel/helper-function-name@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4" integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ== @@ -154,7 +185,7 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-module-transforms@^7.14.5": +"@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz#7de42f10d789b423eb902ebd24031ca77cb1e10e" integrity sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA== @@ -213,14 +244,14 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-split-export-declaration@^7.14.5": +"@babel/helper-split-export-declaration@^7.12.13", "@babel/helper-split-export-declaration@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a" integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA== dependencies: "@babel/types" "^7.14.5" -"@babel/helper-validator-identifier@^7.14.5": +"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== @@ -240,7 +271,7 @@ "@babel/traverse" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/helpers@^7.14.6": +"@babel/helpers@^7.13.10", "@babel/helpers@^7.14.6": version "7.14.6" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.6.tgz#5b58306b95f1b47e2a0199434fa8658fa6c21635" integrity sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA== @@ -258,6 +289,16 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@7.13.10": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.10.tgz#8f8f9bf7b3afa3eabd061f7a5bcdf4fec3c48409" + integrity sha512-0s7Mlrw9uTWkYua7xWr99Wpk2bnGa0ANleKfksYAES8LpWH4gW1OUr42vqKNf0us5UQNfru2wPqMqRITzq/SIQ== + +"@babel/parser@^7.13.0", "@babel/parser@^7.13.10": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.7.tgz#6099720c8839ca865a2637e6c85852ead0bdb595" + integrity sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA== + "@babel/parser@^7.14.5", "@babel/parser@^7.14.6", "@babel/parser@^7.7.0": version "7.14.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.6.tgz#d85cc68ca3cac84eae384c06f032921f5227f4b2" @@ -843,7 +884,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.14.5": +"@babel/template@^7.12.13", "@babel/template@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== @@ -852,6 +893,21 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/traverse@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" + integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.0" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.13.0" + "@babel/types" "^7.13.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.5.tgz#c111b0f58afab4fea3d3385a406f692748c59870" @@ -867,7 +923,16 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.14.5", "@babel/types@^7.4.4", "@babel/types@^7.7.0": +"@babel/types@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" + integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@babel/types@^7.13.0", "@babel/types@^7.14.5", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== @@ -1229,6 +1294,21 @@ resolved "https://registry.yarnpkg.com/@traptitech/markdown-it-spoiler/-/markdown-it-spoiler-1.1.6.tgz#973e92045699551e2c9fb39bbd673ee48bc90b83" integrity sha512-tH/Fk1WMsnSuLpuRsXw8iHtdivoCEI5V08hQ7doVm6WmzAnBf/cUzyH9+GbOldPq9Hwv9v9tuy5t/MxmdNAGXg== +"@trivago/prettier-plugin-sort-imports@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-2.0.2.tgz#62c52462220df6fb35815aaefeaa383fc2535ab1" + integrity sha512-esk6vplzXYwXQs079wBbKog4AFuZfxpJU+MygiijV0wbAibI0tEm+diFFhYP7B2lAaKKdU4+w+BW+McNZCw9HA== + dependencies: + "@babel/core" "7.13.10" + "@babel/generator" "7.13.9" + "@babel/parser" "7.13.10" + "@babel/traverse" "7.13.0" + "@babel/types" "7.13.0" + "@types/lodash" "4.14.168" + javascript-natural-sort "0.7.1" + lodash "4.17.21" + prettier "2.2.1" + "@types/debug@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" @@ -1291,6 +1371,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6" integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q== +"@types/lodash@4.14.168": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + "@types/markdown-it@^12.0.2": version "12.0.2" resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.0.2.tgz#153e5477970ed2a47b2f619ed4ab66f870de8a04" @@ -2732,6 +2817,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +javascript-natural-sort@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= + jest-worker@^26.2.1: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" @@ -2896,7 +2986,7 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: +lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3233,6 +3323,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + prettier@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6"