diff --git a/.stylelintrc.json b/.stylelintrc.json index 1223ef3ad..1a610d5eb 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -9,7 +9,7 @@ "declaration-block-no-redundant-longhand-properties": null, "declaration-colon-newline-after": null, "declaration-empty-line-before": "never", - "font-family-no-missing-generic-family-keyword": [true, { "ignoreFontFamilies": ["ForkAwesome", "Font Awesome 5 Free", "OpenDyslexic", "soapbox"] }], + "font-family-no-missing-generic-family-keyword": [true, { "ignoreFontFamilies": ["ForkAwesome", "Font Awesome 5 Free"] }], "max-line-length": null, "no-descending-specificity": null, "no-duplicate-selectors": null, diff --git a/.tool-versions b/.tool-versions index 5686ee0db..efc600fbd 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 18.12.1 +nodejs 18.13.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index ab6a4c244..3b2a108d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Posts: letterbox images to 19:6 again. - Status Info: moved context (repost, pinned) to improve UX. +- Posts: remove file icon from empty link previews. ### Fixed - Layout: use accent color for "floating action button" (mobile compose button). @@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Modals: close modal when navigating to a different page. - Modals: fix "View context" button in media modal. - Posts: let unauthenticated users to translate posts if allowed by backend. +- Chats: fix jumpy scrollbar. ## [3.0.0] - 2022-12-25 diff --git a/app/application.ts b/app/application.ts deleted file mode 100644 index 9610b4f9a..000000000 --- a/app/application.ts +++ /dev/null @@ -1,17 +0,0 @@ -import loadPolyfills from './soapbox/load-polyfills'; - -// Load iframe event listener -require('./soapbox/iframe'); - -// @ts-ignore -require.context('./assets/images/', true); - -// Load stylesheet -require('react-datepicker/dist/react-datepicker.css'); -require('./styles/application.scss'); - -loadPolyfills().then(() => { - require('./soapbox/main').default(); -}).catch(e => { - console.error(e); -}); diff --git a/app/assets/fonts/OpenDyslexic/LICENSE b/app/assets/fonts/OpenDyslexic/LICENSE deleted file mode 100644 index bb867823f..000000000 --- a/app/assets/fonts/OpenDyslexic/LICENSE +++ /dev/null @@ -1,94 +0,0 @@ -Copyright (c) 2019-07-29, Abbie Gonzalez (https://abbiecod.es|support@abbiecod.es), -with Reserved Font Name OpenDyslexic. -Copyright (c) 12/2012 - 2019 -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Bold-Italic.woff2 b/app/assets/fonts/OpenDyslexic/OpenDyslexic-Bold-Italic.woff2 deleted file mode 100644 index aa7bcdea9..000000000 Binary files a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Bold-Italic.woff2 and /dev/null differ diff --git a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Bold.woff2 b/app/assets/fonts/OpenDyslexic/OpenDyslexic-Bold.woff2 deleted file mode 100644 index 2f04ad119..000000000 Binary files a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Bold.woff2 and /dev/null differ diff --git a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Italic.woff2 b/app/assets/fonts/OpenDyslexic/OpenDyslexic-Italic.woff2 deleted file mode 100644 index 00c19082d..000000000 Binary files a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Italic.woff2 and /dev/null differ diff --git a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Regular.woff2 b/app/assets/fonts/OpenDyslexic/OpenDyslexic-Regular.woff2 deleted file mode 100644 index 47e26d82a..000000000 Binary files a/app/assets/fonts/OpenDyslexic/OpenDyslexic-Regular.woff2 and /dev/null differ diff --git a/app/soapbox/actions/accounts.ts b/app/soapbox/actions/accounts.ts index 14db17275..b4a54201f 100644 --- a/app/soapbox/actions/accounts.ts +++ b/app/soapbox/actions/accounts.ts @@ -10,10 +10,10 @@ import { } from './importer'; import type { AxiosError, CancelToken } from 'axios'; -import type { History } from 'history'; import type { Map as ImmutableMap } from 'immutable'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity, Status } from 'soapbox/types/entities'; +import type { History } from 'soapbox/types/history'; const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST'; const ACCOUNT_CREATE_SUCCESS = 'ACCOUNT_CREATE_SUCCESS'; diff --git a/app/soapbox/actions/chats.ts b/app/soapbox/actions/chats.ts index 67b796408..f4ca85abe 100644 --- a/app/soapbox/actions/chats.ts +++ b/app/soapbox/actions/chats.ts @@ -6,8 +6,8 @@ import { getFeatures } from 'soapbox/utils/features'; import api, { getLinks } from '../api'; -import type { History } from 'history'; import type { AppDispatch, RootState } from 'soapbox/store'; +import type { History } from 'soapbox/types/history'; const CHATS_FETCH_REQUEST = 'CHATS_FETCH_REQUEST'; const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS'; diff --git a/app/soapbox/actions/compose.ts b/app/soapbox/actions/compose.ts index 38efc838e..26768272f 100644 --- a/app/soapbox/actions/compose.ts +++ b/app/soapbox/actions/compose.ts @@ -19,11 +19,11 @@ import { openModal, closeModal } from './modals'; import { getSettings } from './settings'; import { createStatus } from './statuses'; -import type { History } from 'history'; import type { Emoji } from 'soapbox/components/autosuggest-emoji'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { Account, APIEntity, Status, Tag } from 'soapbox/types/entities'; +import type { History } from 'soapbox/types/history'; const { CancelToken, isCancel } = axios; diff --git a/app/soapbox/actions/notifications.ts b/app/soapbox/actions/notifications.ts index 6ac655143..972735e54 100644 --- a/app/soapbox/actions/notifications.ts +++ b/app/soapbox/actions/notifications.ts @@ -107,7 +107,10 @@ const updateNotificationsQueue = (notification: APIEntity, intlMessages: Record< // Desktop notifications try { - if (showAlert && !filtered) { + // eslint-disable-next-line compat/compat + const isNotificationsEnabled = window.Notification?.permission === 'granted'; + + if (showAlert && !filtered && isNotificationsEnabled) { const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); diff --git a/app/soapbox/actions/settings.ts b/app/soapbox/actions/settings.ts index a52ba2255..d6bb970cd 100644 --- a/app/soapbox/actions/settings.ts +++ b/app/soapbox/actions/settings.ts @@ -47,7 +47,6 @@ const defaultSettings = ImmutableMap({ autoloadMore: true, systemFont: false, - dyslexicFont: false, demetricator: false, isDeveloper: false, diff --git a/app/soapbox/base-polyfills.ts b/app/soapbox/base-polyfills.ts deleted file mode 100644 index a6e92bb3c..000000000 --- a/app/soapbox/base-polyfills.ts +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -import 'intl'; -import 'intl/locale-data/jsonp/en'; -import 'es6-symbol/implement'; -// @ts-ignore: No types -import includes from 'array-includes'; -// @ts-ignore: No types -import isNaN from 'is-nan'; -import assign from 'object-assign'; -// @ts-ignore: No types -import values from 'object.values'; - -import { decode as decodeBase64 } from './utils/base64'; - -if (!Array.prototype.includes) { - includes.shim(); -} - -if (!Object.assign) { - Object.assign = assign; -} - -if (!Object.values) { - values.shim(); -} - -if (!Number.isNaN) { - Number.isNaN = isNaN; -} - -if (!HTMLCanvasElement.prototype.toBlob) { - const BASE64_MARKER = ';base64,'; - - Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { - value(callback: any, type = 'image/png', quality: any) { - const dataURL = this.toDataURL(type, quality); - let data; - - if (dataURL.includes(BASE64_MARKER)) { - const [, base64] = dataURL.split(BASE64_MARKER); - data = decodeBase64(base64); - } else { - [, data] = dataURL.split(','); - } - - callback(new Blob([data], { type })); - }, - }); -} diff --git a/app/soapbox/components/domain.tsx b/app/soapbox/components/domain.tsx index 191dc3872..117b94120 100644 --- a/app/soapbox/components/domain.tsx +++ b/app/soapbox/components/domain.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { unblockDomain } from 'soapbox/actions/domain-blocks'; +import { useAppDispatch } from 'soapbox/hooks'; import { HStack, IconButton, Text } from './ui'; @@ -16,7 +16,7 @@ interface IDomain { } const Domain: React.FC = ({ domain }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); // const onBlockDomain = () => { diff --git a/app/soapbox/components/emoji-button-wrapper.tsx b/app/soapbox/components/emoji-button-wrapper.tsx index cc528b194..f0d287aa6 100644 --- a/app/soapbox/components/emoji-button-wrapper.tsx +++ b/app/soapbox/components/emoji-button-wrapper.tsx @@ -1,12 +1,11 @@ import classNames from 'clsx'; import React, { useState, useEffect, useRef } from 'react'; import { usePopper } from 'react-popper'; -import { useDispatch } from 'react-redux'; import { simpleEmojiReact } from 'soapbox/actions/emoji-reacts'; import { openModal } from 'soapbox/actions/modals'; import { EmojiSelector } from 'soapbox/components/ui'; -import { useAppSelector, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks'; import { isUserTouching } from 'soapbox/is-mobile'; import { getReactForStatus } from 'soapbox/utils/emoji-reacts'; @@ -17,7 +16,7 @@ interface IEmojiButtonWrapper { /** Provides emoji reaction functionality to the underlying button component */ const EmojiButtonWrapper: React.FC = ({ statusId, children }): JSX.Element | null => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const ownAccount = useOwnAccount(); const status = useAppSelector(state => state.statuses.get(statusId)); const soapboxConfig = useSoapboxConfig(); diff --git a/app/soapbox/components/icon-button.tsx b/app/soapbox/components/icon-button.tsx index 71f110995..9927ae8df 100644 --- a/app/soapbox/components/icon-button.tsx +++ b/app/soapbox/components/icon-button.tsx @@ -90,7 +90,7 @@ const IconButton: React.FC = ({ type='button' >
-
{text && {text}} diff --git a/app/soapbox/components/icon.tsx b/app/soapbox/components/icon.tsx index f03f40580..8e875ed0e 100644 --- a/app/soapbox/components/icon.tsx +++ b/app/soapbox/components/icon.tsx @@ -1,27 +1,28 @@ /** - * Icon: abstract icon class that can render icons from multiple sets. + * Icon: abstact component to render SVG icons. * @module soapbox/components/icon - * @see soapbox/components/fork_awesome_icon - * @see soapbox/components/svg_icon */ +import classNames from 'clsx'; import React from 'react'; +import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports -import ForkAwesomeIcon, { IForkAwesomeIcon } from './fork-awesome-icon'; -import SvgIcon, { ISvgIcon } from './svg-icon'; +export interface IIcon extends React.HTMLAttributes { + src: string, + id?: string, + alt?: string, + className?: string, +} -export type IIcon = IForkAwesomeIcon | ISvgIcon; - -const Icon: React.FC = (props) => { - if ((props as ISvgIcon).src) { - const { src, ...rest } = (props as ISvgIcon); - - return ; - } else { - const { id, fixedWidth, ...rest } = (props as IForkAwesomeIcon); - - return ; - } +const Icon: React.FC = ({ src, alt, className, ...rest }) => { + return ( +
+ } /> +
+ ); }; export default Icon; diff --git a/app/soapbox/components/media-gallery.tsx b/app/soapbox/components/media-gallery.tsx index 75ff9f798..9b9d1fb7d 100644 --- a/app/soapbox/components/media-gallery.tsx +++ b/app/soapbox/components/media-gallery.tsx @@ -1,5 +1,5 @@ import classNames from 'clsx'; -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useLayoutEffect } from 'react'; import Blurhash from 'soapbox/components/blurhash'; import Icon from 'soapbox/components/icon'; @@ -533,7 +533,7 @@ const MediaGallery: React.FC = (props) => { /> )); - useEffect(() => { + useLayoutEffect(() => { if (node.current) { const { offsetWidth } = node.current; diff --git a/app/soapbox/components/modal-root.tsx b/app/soapbox/components/modal-root.tsx index 388a5393b..33d8ee741 100644 --- a/app/soapbox/components/modal-root.tsx +++ b/app/soapbox/components/modal-root.tsx @@ -11,7 +11,6 @@ import { useAppDispatch, usePrevious } from 'soapbox/hooks'; import { queryClient } from 'soapbox/queries/client'; import { IPolicy, PolicyKeys } from 'soapbox/queries/policies'; -import type { UnregisterCallback } from 'history'; import type { ModalType } from 'soapbox/features/ui/components/modal-root'; import type { ReducerCompose } from 'soapbox/reducers/compose'; import type { ReducerRecord as ReducerComposeEvent } from 'soapbox/reducers/compose-event'; @@ -55,7 +54,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) const ref = useRef(null); const activeElement = useRef(revealed ? document.activeElement as HTMLDivElement | null : null); const modalHistoryKey = useRef(); - const unlistenHistory = useRef(); + const unlistenHistory = useRef>(); const prevChildren = usePrevious(children); const prevType = usePrevious(type); diff --git a/app/soapbox/components/scrollable-list.tsx b/app/soapbox/components/scrollable-list.tsx index 86e40570e..fae4b0f40 100644 --- a/app/soapbox/components/scrollable-list.tsx +++ b/app/soapbox/components/scrollable-list.tsx @@ -24,13 +24,13 @@ type SavedScrollPosition = { // NOTE: It's crucial to space lists with **padding** instead of margin! // Pass an `itemClassName` like `pb-3`, NOT a `space-y-3` className // https://virtuoso.dev/troubleshooting#list-does-not-scroll-to-the-bottom--items-jump-around -const Item: Components['Item'] = ({ context, ...rest }) => ( +const Item: Components['Item'] = ({ context, ...rest }) => (
); /** Custom Virtuoso List component for the outer container. */ // Ensure the className winds up here -const List: Components['List'] = React.forwardRef((props, ref) => { +const List: Components['List'] = React.forwardRef((props, ref) => { const { context, ...rest } = props; return
; }); diff --git a/app/soapbox/components/sidebar-menu.tsx b/app/soapbox/components/sidebar-menu.tsx index 34fd62260..766d3dc87 100644 --- a/app/soapbox/components/sidebar-menu.tsx +++ b/app/soapbox/components/sidebar-menu.tsx @@ -2,7 +2,6 @@ import classNames from 'clsx'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { Link, NavLink } from 'react-router-dom'; import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth'; @@ -11,7 +10,7 @@ import { closeSidebar } from 'soapbox/actions/sidebar'; import Account from 'soapbox/components/account'; import { Stack } from 'soapbox/components/ui'; import ProfileStats from 'soapbox/features/ui/components/profile-stats'; -import { useAppSelector, useFeatures } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { makeGetAccount, makeGetOtherAccounts } from 'soapbox/selectors'; import { Divider, HStack, Icon, IconButton, Text } from './ui'; @@ -81,7 +80,7 @@ const getOtherAccounts = makeGetOtherAccounts(); const SidebarMenu: React.FC = (): JSX.Element | null => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const features = useFeatures(); const getAccount = makeGetAccount(); diff --git a/app/soapbox/components/status-content.tsx b/app/soapbox/components/status-content.tsx index df35a90ac..c361c2289 100644 --- a/app/soapbox/components/status-content.tsx +++ b/app/soapbox/components/status-content.tsx @@ -26,7 +26,7 @@ interface IReadMoreButton { const ReadMoreButton: React.FC = ({ onClick }) => ( ); diff --git a/app/soapbox/components/svg-icon.tsx b/app/soapbox/components/svg-icon.tsx deleted file mode 100644 index cd8942f68..000000000 --- a/app/soapbox/components/svg-icon.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/** - * SvgIcon: abstact component to render SVG icons. - * @module soapbox/components/svg_icon - * @see soapbox/components/icon - */ - -import classNames from 'clsx'; -import React from 'react'; -import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports - -export interface ISvgIcon extends React.HTMLAttributes { - src: string, - id?: string, - alt?: string, - className?: string, -} - -const SvgIcon: React.FC = ({ src, alt, className, ...rest }) => { - return ( -
- } /> -
- ); -}; - -export default SvgIcon; diff --git a/app/soapbox/components/ui/form-group/form-group.tsx b/app/soapbox/components/ui/form-group/form-group.tsx index d93ed6ea8..f2b60f6bd 100644 --- a/app/soapbox/components/ui/form-group/form-group.tsx +++ b/app/soapbox/components/ui/form-group/form-group.tsx @@ -27,7 +27,7 @@ const FormGroup: React.FC = (props) => { if (React.isValidElement(inputChildren[0])) { firstChild = React.cloneElement( inputChildren[0], - { id: formFieldId, hasError }, + { id: formFieldId }, ); } const isCheckboxFormGroup = firstChild?.type === Checkbox; diff --git a/app/soapbox/components/ui/input/input.tsx b/app/soapbox/components/ui/input/input.tsx index 34473fc2c..9087943b6 100644 --- a/app/soapbox/components/ui/input/input.tsx +++ b/app/soapbox/components/ui/input/input.tsx @@ -33,8 +33,6 @@ interface IInput extends Pick, 'maxL value?: string | number, /** Change event handler for the input. */ onChange?: (event: React.ChangeEvent) => void, - /** Whether to display the input in red. */ - hasError?: boolean, /** An element to display as prefix to input. Cannot be used with icon. */ prepend?: React.ReactElement, /** An element to display as suffix to input. Cannot be used with password type. */ @@ -48,7 +46,7 @@ const Input = React.forwardRef( (props, ref) => { const intl = useIntl(); - const { type = 'text', icon, className, outerClassName, hasError, append, prepend, theme = 'normal', ...filteredProps } = props; + const { type = 'text', icon, className, outerClassName, append, prepend, theme = 'normal', ...filteredProps } = props; const [revealed, setRevealed] = React.useState(false); @@ -91,7 +89,6 @@ const Input = React.forwardRef( 'rounded-md bg-white dark:bg-gray-900 border-gray-400 dark:border-gray-800': theme === 'normal', 'rounded-full bg-gray-200 border-gray-200 dark:bg-gray-800 dark:border-gray-800 focus:bg-white': theme === 'search', 'pr-7 rtl:pl-7 rtl:pr-3': isPassword || append, - 'text-red-600 border-red-600': hasError, 'pl-8': typeof icon !== 'undefined', 'pl-16': typeof prepend !== 'undefined', }, className)} diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index 8ce48f7e2..b9603b3b1 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -271,7 +271,6 @@ const SoapboxHead: React.FC = ({ children }) => { const bodyClass = classNames('bg-white dark:bg-gray-800 text-base h-full', { 'no-reduce-motion': !settings.get('reduceMotion'), 'underline-links': settings.get('underlineLinks'), - 'dyslexic': settings.get('dyslexicFont'), 'demetricator': settings.get('demetricator'), }); diff --git a/app/soapbox/contexts/chat-context.tsx b/app/soapbox/contexts/chat-context.tsx index 5b72207c1..8c972a6d0 100644 --- a/app/soapbox/contexts/chat-context.tsx +++ b/app/soapbox/contexts/chat-context.tsx @@ -1,9 +1,8 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { useHistory, useParams } from 'react-router-dom'; import { toggleMainWindow } from 'soapbox/actions/chats'; -import { useOwnAccount, useSettings } from 'soapbox/hooks'; +import { useAppDispatch, useOwnAccount, useSettings } from 'soapbox/hooks'; import { IChat, useChat } from 'soapbox/queries/chats'; type WindowState = 'open' | 'minimized'; @@ -22,7 +21,7 @@ enum ChatWidgetScreens { const ChatProvider: React.FC = ({ children }) => { const history = useHistory(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const settings = useSettings(); const account = useOwnAccount(); diff --git a/app/soapbox/extra-polyfills.ts b/app/soapbox/extra-polyfills.ts deleted file mode 100644 index c4f6da408..000000000 --- a/app/soapbox/extra-polyfills.ts +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -import 'intersection-observer'; -import 'requestidlecallback'; diff --git a/app/soapbox/features/account-gallery/index.tsx b/app/soapbox/features/account-gallery/index.tsx index 82d356386..c155e64bb 100644 --- a/app/soapbox/features/account-gallery/index.tsx +++ b/app/soapbox/features/account-gallery/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { useParams } from 'react-router-dom'; @@ -66,10 +66,7 @@ const AccountGallery = () => { const hasMore = useAppSelector((state) => state.timelines.get(`account:${accountId}:media`)?.hasMore); const [width, setWidth] = useState(323); - - const handleRef = (c: HTMLDivElement) => { - if (c) setWidth(c.offsetWidth); - }; + const node = useRef(null); const handleScrollToBottom = () => { if (hasMore) { @@ -99,6 +96,12 @@ const AccountGallery = () => { } }; + useLayoutEffect(() => { + if (node.current) { + setWidth(node.current.offsetWidth); + } + }, [node.current]); + useEffect(() => { if (accountId && accountId !== -1) { dispatch(fetchAccount(accountId)); @@ -140,7 +143,7 @@ const AccountGallery = () => { return ( -
+
{attachments.map((attachment, index) => attachment === null ? ( 0 ? (attachments.get(index - 1)?.id || null) : null} onLoadMore={handleLoadMore} /> ) : ( diff --git a/app/soapbox/features/aliases/components/search.tsx b/app/soapbox/features/aliases/components/search.tsx index f64c4a332..4a2ca3c76 100644 --- a/app/soapbox/features/aliases/components/search.tsx +++ b/app/soapbox/features/aliases/components/search.tsx @@ -1,12 +1,11 @@ import classNames from 'clsx'; import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { fetchAliasesSuggestions, clearAliasesSuggestions, changeAliasesSuggestions } from 'soapbox/actions/aliases'; import Icon from 'soapbox/components/icon'; import { Button } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; const messages = defineMessages({ search: { id: 'aliases.search', defaultMessage: 'Search your old account' }, @@ -14,7 +13,7 @@ const messages = defineMessages({ }); const Search: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const value = useAppSelector(state => state.aliases.suggestions.value); diff --git a/app/soapbox/features/audio/index.tsx b/app/soapbox/features/audio/index.tsx index ecbae5b16..17854386e 100644 --- a/app/soapbox/features/audio/index.tsx +++ b/app/soapbox/features/audio/index.tsx @@ -1,7 +1,7 @@ import classNames from 'clsx'; import debounce from 'lodash/debounce'; import throttle from 'lodash/throttle'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import Icon from 'soapbox/components/icon'; @@ -397,7 +397,7 @@ const Audio: React.FC = (props) => { const progress = Math.min((currentTime / getDuration()) * 100, 100); - useEffect(() => { + useLayoutEffect(() => { if (player.current) { _setDimensions(); } diff --git a/app/soapbox/features/auth-login/components/registration-form.tsx b/app/soapbox/features/auth-login/components/registration-form.tsx index 0f825ea8c..248f0b411 100644 --- a/app/soapbox/features/auth-login/components/registration-form.tsx +++ b/app/soapbox/features/auth-login/components/registration-form.tsx @@ -238,7 +238,6 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { pattern='^[a-zA-Z\d_-]+' onChange={onUsernameChange} value={params.get('username', '')} - hasError={usernameUnavailable} required /> diff --git a/app/soapbox/features/blocks/index.tsx b/app/soapbox/features/blocks/index.tsx index 730ef3643..c9d8a50c5 100644 --- a/app/soapbox/features/blocks/index.tsx +++ b/app/soapbox/features/blocks/index.tsx @@ -1,13 +1,12 @@ import debounce from 'lodash/debounce'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { fetchBlocks, expandBlocks } from 'soapbox/actions/blocks'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; const messages = defineMessages({ heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }, @@ -18,7 +17,7 @@ const handleLoadMore = debounce((dispatch) => { }, 300, { leading: true }); const Blocks: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const accountIds = useAppSelector((state) => state.user_lists.blocks.items); diff --git a/app/soapbox/features/chats/components/chat-message-list.tsx b/app/soapbox/features/chats/components/chat-message-list.tsx index 4b5098a7d..cdf032178 100644 --- a/app/soapbox/features/chats/components/chat-message-list.tsx +++ b/app/soapbox/features/chats/components/chat-message-list.tsx @@ -66,6 +66,21 @@ const List: Components['List'] = React.forwardRef((props, ref) => { return
; }); +const Scroller: Components['Scroller'] = React.forwardRef((props, ref) => { + const { style, context, ...rest } = props; + + return ( +
+ ); +}); + interface IChatMessageList { /** Chat the messages are being rendered from. */ chat: IChat, @@ -472,6 +487,7 @@ const ChatMessageList: React.FC = ({ chat }) => { }} components={{ List, + Scroller, Header: () => { if (hasNextPage || isFetchingNextPage) { return ; diff --git a/app/soapbox/features/chats/components/chat-page/__tests__/chat-page.test.tsx b/app/soapbox/features/chats/components/chat-page/__tests__/chat-page.test.tsx index ddac6c8bc..21ed9ddb0 100644 --- a/app/soapbox/features/chats/components/chat-page/__tests__/chat-page.test.tsx +++ b/app/soapbox/features/chats/components/chat-page/__tests__/chat-page.test.tsx @@ -6,7 +6,7 @@ import { __stub } from 'soapbox/api'; import { normalizeAccount } from 'soapbox/normalizers'; import { ReducerAccount } from 'soapbox/reducers/accounts'; -import { render, screen } from '../../../../../jest/test-helpers'; +import { render, screen, waitFor } from '../../../../../jest/test-helpers'; import ChatPage from '../chat-page'; describe('', () => { @@ -48,7 +48,10 @@ describe('', () => { await userEvent.click(screen.getByTestId('button')); expect(screen.getByTestId('chat-page')).toBeInTheDocument(); - expect(screen.getByTestId('toast')).toHaveTextContent('Chat Settings updated successfully'); + + await waitFor(() => { + expect(screen.getByTestId('toast')).toHaveTextContent('Chat Settings updated successfully'); + }); }); }); @@ -77,7 +80,10 @@ describe('', () => { it('renders the Chats', async () => { render(, undefined, store); await userEvent.click(screen.getByTestId('button')); - expect(screen.getByTestId('toast')).toHaveTextContent('Chat Settings failed to update.'); + + await waitFor(() => { + expect(screen.getByTestId('toast')).toHaveTextContent('Chat Settings failed to update.'); + }); }); }); }); diff --git a/app/soapbox/features/chats/components/chat-page/chat-page.tsx b/app/soapbox/features/chats/components/chat-page/chat-page.tsx index 407d49e7b..dbe50d385 100644 --- a/app/soapbox/features/chats/components/chat-page/chat-page.tsx +++ b/app/soapbox/features/chats/components/chat-page/chat-page.tsx @@ -1,5 +1,5 @@ import classNames from 'clsx'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { matchPath, Route, Switch, useHistory } from 'react-router-dom'; import { Stack } from 'soapbox/components/ui'; @@ -44,7 +44,7 @@ const ChatPage: React.FC = ({ chatId }) => { setHeight(fullHeight - top + offset); }; - useEffect(() => { + useLayoutEffect(() => { calculateHeight(); }, [containerRef.current]); diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx index 638a7fea1..4af6b0240 100644 --- a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx @@ -238,7 +238,7 @@ const ChatPageMain = () => {
diff --git a/app/soapbox/features/compose/components/search.tsx b/app/soapbox/features/compose/components/search.tsx index 796100440..5c7db5f05 100644 --- a/app/soapbox/features/compose/components/search.tsx +++ b/app/soapbox/features/compose/components/search.tsx @@ -1,9 +1,7 @@ import classNames from 'clsx'; -import { Map as ImmutableMap } from 'immutable'; import debounce from 'lodash/debounce'; import React, { useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { @@ -17,7 +15,8 @@ import { import AutosuggestAccountInput from 'soapbox/components/autosuggest-account-input'; import { Input } from 'soapbox/components/ui'; import SvgIcon from 'soapbox/components/ui/icon/svg-icon'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { AppDispatch, RootState } from 'soapbox/store'; const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, @@ -25,7 +24,7 @@ const messages = defineMessages({ }); function redirectToAccount(accountId: string, routerHistory: any) { - return (_dispatch: any, getState: () => ImmutableMap) => { + return (_dispatch: AppDispatch, getState: () => RootState) => { const acct = getState().getIn(['accounts', accountId, 'acct']); if (acct && routerHistory) { @@ -49,7 +48,7 @@ const Search = (props: ISearch) => { openInRoute = false, } = props; - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const history = useHistory(); const intl = useIntl(); diff --git a/app/soapbox/features/developers/developers-challenge.tsx b/app/soapbox/features/developers/developers-challenge.tsx index e431f3242..ee83ba809 100644 --- a/app/soapbox/features/developers/developers-challenge.tsx +++ b/app/soapbox/features/developers/developers-challenge.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { changeSettingImmediate } from 'soapbox/actions/settings'; import { Column, Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; import toast from 'soapbox/toast'; const messages = defineMessages({ @@ -15,7 +15,7 @@ const messages = defineMessages({ }); const DevelopersChallenge = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const [answer, setAnswer] = useState(''); diff --git a/app/soapbox/features/developers/developers-menu.tsx b/app/soapbox/features/developers/developers-menu.tsx index 070c0f95e..61e8c1de6 100644 --- a/app/soapbox/features/developers/developers-menu.tsx +++ b/app/soapbox/features/developers/developers-menu.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { Link, useHistory } from 'react-router-dom'; import { changeSettingImmediate } from 'soapbox/actions/settings'; import { Column, Text } from 'soapbox/components/ui'; import SvgIcon from 'soapbox/components/ui/icon/svg-icon'; +import { useAppDispatch } from 'soapbox/hooks'; import toast from 'soapbox/toast'; import sourceCode from 'soapbox/utils/code'; @@ -31,7 +31,7 @@ const DashWidget: React.FC = ({ to, onClick, children }) => { }; const Developers: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const history = useHistory(); const intl = useIntl(); diff --git a/app/soapbox/features/developers/settings-store.tsx b/app/soapbox/features/developers/settings-store.tsx index 34caab1b0..a86a6b94c 100644 --- a/app/soapbox/features/developers/settings-store.tsx +++ b/app/soapbox/features/developers/settings-store.tsx @@ -134,12 +134,6 @@ const SettingsStore: React.FC = () => { -
- }> - - -
- } hint={} diff --git a/app/soapbox/features/directory/index.tsx b/app/soapbox/features/directory/index.tsx index 70d227868..13ad6b816 100644 --- a/app/soapbox/features/directory/index.tsx +++ b/app/soapbox/features/directory/index.tsx @@ -1,13 +1,12 @@ import classNames from 'clsx'; import React, { useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { fetchDirectory, expandDirectory } from 'soapbox/actions/directory'; import LoadMore from 'soapbox/components/load-more'; import { Column, RadioButton, Stack, Text } from 'soapbox/components/ui'; -import { useAppSelector, useFeatures, useInstance } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures, useInstance } from 'soapbox/hooks'; import AccountCard from './components/account-card'; @@ -21,7 +20,7 @@ const messages = defineMessages({ const Directory = () => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { search } = useLocation(); const params = new URLSearchParams(search); const instance = useInstance(); diff --git a/app/soapbox/features/domain-blocks/index.tsx b/app/soapbox/features/domain-blocks/index.tsx index ee96f2c56..17d6f4757 100644 --- a/app/soapbox/features/domain-blocks/index.tsx +++ b/app/soapbox/features/domain-blocks/index.tsx @@ -1,13 +1,12 @@ import debounce from 'lodash/debounce'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { fetchDomainBlocks, expandDomainBlocks } from 'soapbox/actions/domain-blocks'; import Domain from 'soapbox/components/domain'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, Spinner } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' }, @@ -19,7 +18,7 @@ const handleLoadMore = debounce((dispatch) => { }, 300, { leading: true }); const DomainBlocks: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const domains = useAppSelector((state) => state.domain_lists.blocks.items); diff --git a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx index 71d1c9f29..bb6048edb 100644 --- a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx +++ b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx @@ -78,6 +78,9 @@ describe('', () => { expect(screen.getAllByTestId('carousel-item-avatar')[0]).toHaveClass('ring-primary-600'); }); + // HACK: wait for state change + await new Promise((r) => setTimeout(r, 0)); + // Marked as seen, not selected await userEvent.click(screen.getAllByTestId('carousel-item-avatar')[0]); await waitFor(() => { diff --git a/app/soapbox/features/follow-requests/components/account-authorize.tsx b/app/soapbox/features/follow-requests/components/account-authorize.tsx index b73d0a719..b99f4c0fc 100644 --- a/app/soapbox/features/follow-requests/components/account-authorize.tsx +++ b/app/soapbox/features/follow-requests/components/account-authorize.tsx @@ -1,11 +1,10 @@ import React, { useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { authorizeFollowRequest, rejectFollowRequest } from 'soapbox/actions/accounts'; import Account from 'soapbox/components/account'; import { Button, HStack } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; const messages = defineMessages({ @@ -19,7 +18,7 @@ interface IAccountAuthorize { const AccountAuthorize: React.FC = ({ id }) => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const getAccount = useCallback(makeGetAccount(), []); diff --git a/app/soapbox/features/follow-requests/index.tsx b/app/soapbox/features/follow-requests/index.tsx index 3d8700900..f89681a01 100644 --- a/app/soapbox/features/follow-requests/index.tsx +++ b/app/soapbox/features/follow-requests/index.tsx @@ -1,12 +1,11 @@ import debounce from 'lodash/debounce'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { fetchFollowRequests, expandFollowRequests } from 'soapbox/actions/accounts'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, Spinner } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import AccountAuthorize from './components/account-authorize'; @@ -19,7 +18,7 @@ const handleLoadMore = debounce((dispatch) => { }, 300, { leading: true }); const FollowRequests: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const accountIds = useAppSelector((state) => state.user_lists.follow_requests.items); diff --git a/app/soapbox/features/list-adder/components/list.tsx b/app/soapbox/features/list-adder/components/list.tsx index f1fb3ee40..b02800c92 100644 --- a/app/soapbox/features/list-adder/components/list.tsx +++ b/app/soapbox/features/list-adder/components/list.tsx @@ -37,7 +37,7 @@ const List: React.FC = ({ listId }) => { return (
- + {list.title} diff --git a/app/soapbox/features/lists/components/new-list-form.tsx b/app/soapbox/features/lists/components/new-list-form.tsx index 645d6199c..2f3ef50ba 100644 --- a/app/soapbox/features/lists/components/new-list-form.tsx +++ b/app/soapbox/features/lists/components/new-list-form.tsx @@ -1,10 +1,9 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { changeListEditorTitle, submitListEditor } from 'soapbox/actions/lists'; import { Button, Form, HStack, Input } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; const messages = defineMessages({ label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' }, @@ -13,7 +12,7 @@ const messages = defineMessages({ }); const NewListForm: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const value = useAppSelector((state) => state.listEditor.get('title')); diff --git a/app/soapbox/features/lists/index.tsx b/app/soapbox/features/lists/index.tsx index 082da7c5d..5b6fed627 100644 --- a/app/soapbox/features/lists/index.tsx +++ b/app/soapbox/features/lists/index.tsx @@ -1,6 +1,5 @@ import React, { useEffect } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; import { createSelector } from 'reselect'; @@ -9,7 +8,7 @@ import { openModal } from 'soapbox/actions/modals'; import Icon from 'soapbox/components/icon'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, IconButton, Spinner } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import NewListForm from './components/new-list-form'; @@ -35,7 +34,7 @@ const getOrderedLists = createSelector([(state: RootState) => state.lists], list }); const Lists: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const lists = useAppSelector((state) => getOrderedLists(state)); @@ -85,7 +84,7 @@ const Lists: React.FC = () => { > {lists.map((list: any) => ( - + {list.title} diff --git a/app/soapbox/features/mutes/index.tsx b/app/soapbox/features/mutes/index.tsx index e7e8365aa..818fdec57 100644 --- a/app/soapbox/features/mutes/index.tsx +++ b/app/soapbox/features/mutes/index.tsx @@ -1,13 +1,12 @@ import debounce from 'lodash/debounce'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { fetchMutes, expandMutes } from 'soapbox/actions/mutes'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; const messages = defineMessages({ heading: { id: 'column.mutes', defaultMessage: 'Muted users' }, @@ -18,7 +17,7 @@ const handleLoadMore = debounce((dispatch) => { }, 300, { leading: true }); const Mutes: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const accountIds = useAppSelector((state) => state.user_lists.mutes.items); diff --git a/app/soapbox/features/onboarding/onboarding-wizard.tsx b/app/soapbox/features/onboarding/onboarding-wizard.tsx index f89204885..25e4ad128 100644 --- a/app/soapbox/features/onboarding/onboarding-wizard.tsx +++ b/app/soapbox/features/onboarding/onboarding-wizard.tsx @@ -1,12 +1,11 @@ import classNames from 'clsx'; import React from 'react'; -import { useDispatch } from 'react-redux'; import ReactSwipeableViews from 'react-swipeable-views'; import { endOnboarding } from 'soapbox/actions/onboarding'; import LandingGradient from 'soapbox/components/landing-gradient'; import { HStack } from 'soapbox/components/ui'; -import { useFeatures } from 'soapbox/hooks'; +import { useAppDispatch, useFeatures } from 'soapbox/hooks'; import AvatarSelectionStep from './steps/avatar-selection-step'; import BioStep from './steps/bio-step'; @@ -17,7 +16,7 @@ import FediverseStep from './steps/fediverse-step'; import SuggestedAccountsStep from './steps/suggested-accounts-step'; const OnboardingWizard = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const features = useFeatures(); const [currentStep, setCurrentStep] = React.useState(0); diff --git a/app/soapbox/features/onboarding/steps/avatar-selection-step.tsx b/app/soapbox/features/onboarding/steps/avatar-selection-step.tsx index b2c17fe64..ab9956dac 100644 --- a/app/soapbox/features/onboarding/steps/avatar-selection-step.tsx +++ b/app/soapbox/features/onboarding/steps/avatar-selection-step.tsx @@ -1,11 +1,10 @@ import classNames from 'clsx'; import React from 'react'; import { defineMessages, FormattedMessage } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { patchMe } from 'soapbox/actions/me'; import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui'; -import { useOwnAccount } from 'soapbox/hooks'; +import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import toast from 'soapbox/toast'; import { isDefaultAvatar } from 'soapbox/utils/accounts'; import resizeImage from 'soapbox/utils/resize-image'; @@ -17,7 +16,7 @@ const messages = defineMessages({ }); const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const account = useOwnAccount(); const fileInput = React.useRef(null); diff --git a/app/soapbox/features/onboarding/steps/bio-step.tsx b/app/soapbox/features/onboarding/steps/bio-step.tsx index cf090a45f..d0a5e37a9 100644 --- a/app/soapbox/features/onboarding/steps/bio-step.tsx +++ b/app/soapbox/features/onboarding/steps/bio-step.tsx @@ -1,10 +1,9 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { patchMe } from 'soapbox/actions/me'; import { Button, Card, CardBody, FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui'; -import { useOwnAccount } from 'soapbox/hooks'; +import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import toast from 'soapbox/toast'; import type { AxiosError } from 'axios'; @@ -16,7 +15,7 @@ const messages = defineMessages({ const BioStep = ({ onNext }: { onNext: () => void }) => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const account = useOwnAccount(); const [value, setValue] = React.useState(account?.source.get('note') || ''); diff --git a/app/soapbox/features/onboarding/steps/cover-photo-selection-step.tsx b/app/soapbox/features/onboarding/steps/cover-photo-selection-step.tsx index 4cf2493ad..d7b213247 100644 --- a/app/soapbox/features/onboarding/steps/cover-photo-selection-step.tsx +++ b/app/soapbox/features/onboarding/steps/cover-photo-selection-step.tsx @@ -1,12 +1,11 @@ import classNames from 'clsx'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { patchMe } from 'soapbox/actions/me'; import StillImage from 'soapbox/components/still-image'; import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui'; -import { useOwnAccount } from 'soapbox/hooks'; +import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import toast from 'soapbox/toast'; import { isDefaultHeader } from 'soapbox/utils/accounts'; import resizeImage from 'soapbox/utils/resize-image'; @@ -20,7 +19,7 @@ const messages = defineMessages({ const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const account = useOwnAccount(); const fileInput = React.useRef(null); diff --git a/app/soapbox/features/onboarding/steps/display-name-step.tsx b/app/soapbox/features/onboarding/steps/display-name-step.tsx index 7d2b13925..246ed3b93 100644 --- a/app/soapbox/features/onboarding/steps/display-name-step.tsx +++ b/app/soapbox/features/onboarding/steps/display-name-step.tsx @@ -1,10 +1,9 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { patchMe } from 'soapbox/actions/me'; import { Button, Card, CardBody, FormGroup, Input, Stack, Text } from 'soapbox/components/ui'; -import { useOwnAccount } from 'soapbox/hooks'; +import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import toast from 'soapbox/toast'; import type { AxiosError } from 'axios'; @@ -16,7 +15,7 @@ const messages = defineMessages({ const DisplayNameStep = ({ onNext }: { onNext: () => void }) => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const account = useOwnAccount(); const [value, setValue] = React.useState(account?.display_name || ''); diff --git a/app/soapbox/features/preferences/index.tsx b/app/soapbox/features/preferences/index.tsx index ebfd02547..09ea08eea 100644 --- a/app/soapbox/features/preferences/index.tsx +++ b/app/soapbox/features/preferences/index.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { changeSetting } from 'soapbox/actions/settings'; import List, { ListItem } from 'soapbox/components/list'; import { Form } from 'soapbox/components/ui'; import { SelectDropdown } from 'soapbox/features/forms'; import SettingToggle from 'soapbox/features/notifications/components/setting-toggle'; -import { useFeatures, useSettings } from 'soapbox/hooks'; +import { useAppDispatch, useFeatures, useSettings } from 'soapbox/hooks'; import ThemeToggle from '../ui/components/theme-toggle'; @@ -89,7 +88,7 @@ const messages = defineMessages({ const Preferences = () => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const features = useFeatures(); const settings = useSettings(); diff --git a/app/soapbox/features/public-layout/components/header.tsx b/app/soapbox/features/public-layout/components/header.tsx index 4fef3df36..49f801d84 100644 --- a/app/soapbox/features/public-layout/components/header.tsx +++ b/app/soapbox/features/public-layout/components/header.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { Link, Redirect } from 'react-router-dom'; import { logIn, verifyCredentials } from 'soapbox/actions/auth'; @@ -8,7 +7,7 @@ import { fetchInstance } from 'soapbox/actions/instance'; import { openModal } from 'soapbox/actions/modals'; import SiteLogo from 'soapbox/components/site-logo'; import { Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui'; -import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount, useInstance } from 'soapbox/hooks'; +import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount, useInstance, useAppDispatch } from 'soapbox/hooks'; import Sonar from './sonar'; @@ -25,7 +24,7 @@ const messages = defineMessages({ }); const Header = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const account = useOwnAccount(); diff --git a/app/soapbox/features/quotes/index.tsx b/app/soapbox/features/quotes/index.tsx index a93fc8317..6ba38fe10 100644 --- a/app/soapbox/features/quotes/index.tsx +++ b/app/soapbox/features/quotes/index.tsx @@ -2,13 +2,12 @@ import { OrderedSet as ImmutableOrderedSet } from 'immutable'; import { debounce } from 'lodash'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { useParams } from 'react-router-dom'; import { expandStatusQuotes, fetchStatusQuotes } from 'soapbox/actions/status-quotes'; import StatusList from 'soapbox/components/status-list'; import { Column } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; const messages = defineMessages({ heading: { id: 'column.quotes', defaultMessage: 'Post quotes' }, @@ -18,7 +17,7 @@ const handleLoadMore = debounce((statusId: string, dispatch: React.Dispatch dispatch(expandStatusQuotes(statusId)), 300, { leading: true }); const Quotes: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const intl = useIntl(); const { statusId } = useParams<{ statusId: string }>(); diff --git a/app/soapbox/features/settings/index.tsx b/app/soapbox/features/settings/index.tsx index 06b8bf8a2..a0842984a 100644 --- a/app/soapbox/features/settings/index.tsx +++ b/app/soapbox/features/settings/index.tsx @@ -1,12 +1,11 @@ import React, { useEffect } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { fetchMfa } from 'soapbox/actions/mfa'; import List, { ListItem } from 'soapbox/components/list'; import { Card, CardBody, CardHeader, CardTitle, Column } from 'soapbox/components/ui'; -import { useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; import Preferences from '../preferences'; @@ -32,7 +31,7 @@ const messages = defineMessages({ /** User settings page. */ const Settings = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const history = useHistory(); const intl = useIntl(); diff --git a/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx b/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx index d23e9f004..9a5f90399 100644 --- a/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx +++ b/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx @@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl'; // @ts-ignore import Overlay from 'react-overlays/lib/Overlay'; -import Icon from 'soapbox/components/icon'; +import ForkAwesomeIcon from 'soapbox/components/fork-awesome-icon'; import IconPickerMenu from './icon-picker-menu'; @@ -68,7 +68,7 @@ const IconPickerDropdown: React.FC = ({ value, onPickEmoji onKeyDown={onToggle} tabIndex={0} > - +
diff --git a/app/soapbox/features/soapbox-config/index.tsx b/app/soapbox/features/soapbox-config/index.tsx index bb96fd6bc..ae224cf70 100644 --- a/app/soapbox/features/soapbox-config/index.tsx +++ b/app/soapbox/features/soapbox-config/index.tsx @@ -39,6 +39,7 @@ const messages = defineMessages({ customCssLabel: { id: 'soapbox_config.custom_css.meta_fields.url_placeholder', defaultMessage: 'URL' }, rawJSONLabel: { id: 'soapbox_config.raw_json_label', defaultMessage: 'Advanced: Edit raw JSON data' }, rawJSONHint: { id: 'soapbox_config.raw_json_hint', defaultMessage: 'Edit the settings data directly. Changes made directly to the JSON file will override the form fields above. Click "Save" to apply your changes.' }, + rawJSONInvalid: { id: 'soapbox_config.raw_json_invalid', defaultMessage: 'is invalid' }, verifiedCanEditNameLabel: { id: 'soapbox_config.verified_can_edit_name_label', defaultMessage: 'Allow verified users to edit their own display name.' }, displayFqnLabel: { id: 'soapbox_config.display_fqn_label', defaultMessage: 'Display domain (eg @user@domain) for local accounts.' }, greentextLabel: { id: 'soapbox_config.greentext_label', defaultMessage: 'Enable greentext support' }, @@ -394,11 +395,13 @@ const SoapboxConfig: React.FC = () => { expanded={jsonEditorExpanded} onToggle={toggleJSONEditor} > - +