diff --git a/app/assets/icons/COPYING.md b/app/assets/icons/COPYING.md index 5e84c0b5cc..1dcc928d92 100644 --- a/app/assets/icons/COPYING.md +++ b/app/assets/icons/COPYING.md @@ -1,6 +1,5 @@ # Custom icons -- fediverse.svg - Modified from Wikipedia, CC0 - verified.svg - Created by Alex Gleason. CC0 Fediverse logo: https://en.wikipedia.org/wiki/Fediverse#/media/File:Fediverse_logo_proposal.svg diff --git a/app/assets/icons/fediverse.svg b/app/assets/icons/fediverse.svg deleted file mode 100644 index 4cd3cb9380..0000000000 --- a/app/assets/icons/fediverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/soapbox/components/__mocks__/react-inlinesvg.tsx b/app/soapbox/components/__mocks__/react-inlinesvg.tsx index 367ec0e333..1d4fde154e 100644 --- a/app/soapbox/components/__mocks__/react-inlinesvg.tsx +++ b/app/soapbox/components/__mocks__/react-inlinesvg.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; interface IInlineSVG { loader?: JSX.Element, diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index 006584e8d1..70ab2cfc55 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { Link, useHistory } from 'react-router-dom'; import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper'; @@ -199,7 +199,7 @@ const Account = ({ title={account.acct} onClick={(event: React.MouseEvent) => event.stopPropagation()} > -
+ {account.verified && } -
+ @@ -255,7 +255,7 @@ const Account = ({ )} diff --git a/app/soapbox/components/copyable-input.tsx b/app/soapbox/components/copyable-input.tsx index c38cc8c5b3..24253d7df3 100644 --- a/app/soapbox/components/copyable-input.tsx +++ b/app/soapbox/components/copyable-input.tsx @@ -28,7 +28,7 @@ const CopyableInput: React.FC = ({ value }) => { ref={input} type='text' value={value} - className='rounded-r-none' + className='rounded-r-none rtl:rounded-l-none rtl:rounded-r-lg' outerClassName='flex-grow' onClick={selectInput} readOnly @@ -36,7 +36,7 @@ const CopyableInput: React.FC = ({ value }) => { ); diff --git a/app/soapbox/components/ui/avatar/avatar.tsx b/app/soapbox/components/ui/avatar/avatar.tsx index ec19809787..384fa354df 100644 --- a/app/soapbox/components/ui/avatar/avatar.tsx +++ b/app/soapbox/components/ui/avatar/avatar.tsx @@ -1,5 +1,5 @@ import classNames from 'clsx'; -import * as React from 'react'; +import React from 'react'; import StillImage from 'soapbox/components/still-image'; @@ -25,7 +25,7 @@ const Avatar = (props: IAvatar) => { return ( = ({ children, backHref, onBackClick }): const backAttributes = backHref ? { to: backHref } : { onClick: onBackClick }; return ( - + {intl.formatMessage(messages.back)} @@ -70,11 +70,11 @@ const CardHeader: React.FC = ({ children, backHref, onBackClick }): }; return ( -
+ {renderBackButton()} {children} -
+ ); }; diff --git a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx index 20810efbe4..caaea79ee6 100644 --- a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx +++ b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx @@ -47,8 +47,7 @@ const EmojiSelector: React.FC = ({ emojis, onReact, visible = fa return ( {Array.from(emojis).map((emoji, i) => ( ( -
+ {children} -
+
); export default FormActions; diff --git a/app/soapbox/components/ui/form/form.tsx b/app/soapbox/components/ui/form/form.tsx index 59ca180b1f..466825434c 100644 --- a/app/soapbox/components/ui/form/form.tsx +++ b/app/soapbox/components/ui/form/form.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; interface IForm { /** Form submission event handler. */ diff --git a/app/soapbox/components/ui/hstack/hstack.tsx b/app/soapbox/components/ui/hstack/hstack.tsx index eee5b37062..d648c036e4 100644 --- a/app/soapbox/components/ui/hstack/hstack.tsx +++ b/app/soapbox/components/ui/hstack/hstack.tsx @@ -22,8 +22,10 @@ const spaces = { 1: 'space-x-1', 1.5: 'space-x-1.5', 2: 'space-x-2', + 2.5: 'space-x-2.5', 3: 'space-x-3', 4: 'space-x-4', + 5: 'space-x-5', 6: 'space-x-6', 8: 'space-x-8', }; @@ -59,7 +61,7 @@ const HStack = forwardRef((props, ref) => { ( ['normal', 'search'].includes(theme), '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': isPassword || append, + '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)} /> - {/* eslint-disable-next-line no-nested-ternary */} {append ? ( -
+
{append}
) : null} @@ -112,7 +111,7 @@ const Input = React.forwardRef( intl.formatMessage(messages.showPassword) } > -
+
-
+
)}
diff --git a/app/soapbox/components/ui/radio-button/radio-button.tsx b/app/soapbox/components/ui/radio-button/radio-button.tsx index 322cbd7e75..96f2c2ec44 100644 --- a/app/soapbox/components/ui/radio-button/radio-button.tsx +++ b/app/soapbox/components/ui/radio-button/radio-button.tsx @@ -1,6 +1,8 @@ import React, { useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; +import HStack from '../hstack/hstack'; + interface IRadioButton { value: string checked?: boolean @@ -16,7 +18,7 @@ const RadioButton: React.FC = ({ name, value, checked, onChange, l const formFieldId: string = useMemo(() => `radio-${uuidv4()}`, []); return ( -
+ = ({ name, value, checked, onChange, l className='h-4 w-4 border-gray-300 text-primary-600 focus:ring-primary-500' /> -
+ ); }; diff --git a/app/soapbox/components/ui/select/select.tsx b/app/soapbox/components/ui/select/select.tsx index b8d05b79da..8c2369ce56 100644 --- a/app/soapbox/components/ui/select/select.tsx +++ b/app/soapbox/components/ui/select/select.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; interface ISelect extends React.SelectHTMLAttributes { children: Iterable, diff --git a/app/soapbox/components/ui/tabs/tabs.tsx b/app/soapbox/components/ui/tabs/tabs.tsx index 49316529af..2839085799 100644 --- a/app/soapbox/components/ui/tabs/tabs.tsx +++ b/app/soapbox/components/ui/tabs/tabs.tsx @@ -6,7 +6,7 @@ import { useTabsContext, } from '@reach/tabs'; import classNames from 'clsx'; -import * as React from 'react'; +import React from 'react'; import { useHistory } from 'react-router-dom'; import Counter from '../counter/counter'; diff --git a/app/soapbox/components/ui/textarea/textarea.tsx b/app/soapbox/components/ui/textarea/textarea.tsx index 7c2f94a4ab..8103259738 100644 --- a/app/soapbox/components/ui/textarea/textarea.tsx +++ b/app/soapbox/components/ui/textarea/textarea.tsx @@ -1,7 +1,7 @@ import classNames from 'clsx'; import React, { useState } from 'react'; -interface ITextarea extends Pick, 'maxLength' | 'onChange' | 'onKeyDown' | 'required' | 'disabled' | 'rows' | 'readOnly'> { +interface ITextarea extends Pick, 'maxLength' | 'onChange' | 'required' | 'disabled' | 'rows' | 'readOnly' | 'onKeyDown' | 'onPaste'> { /** Put the cursor into the input on mount. */ autoFocus?: boolean, /** Allows the textarea height to grow while typing */ diff --git a/app/soapbox/components/ui/widget/widget.tsx b/app/soapbox/components/ui/widget/widget.tsx index 3bced193fa..16cc33234f 100644 --- a/app/soapbox/components/ui/widget/widget.tsx +++ b/app/soapbox/components/ui/widget/widget.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import { Text, IconButton } from 'soapbox/components/ui'; -import HStack from 'soapbox/components/ui/hstack/hstack'; -import Stack from 'soapbox/components/ui/stack/stack'; +import { HStack, IconButton, Stack, Text } from 'soapbox/components/ui'; interface IWidgetTitle { /** Title text for the widget. */ diff --git a/app/soapbox/components/verification-badge.tsx b/app/soapbox/components/verification-badge.tsx index 766decb883..aea2f5f810 100644 --- a/app/soapbox/components/verification-badge.tsx +++ b/app/soapbox/components/verification-badge.tsx @@ -2,7 +2,7 @@ import classNames from 'clsx'; import React from 'react'; import { useIntl, defineMessages } from 'react-intl'; -import Icon from 'soapbox/components/ui/icon/icon'; +import { Icon } from 'soapbox/components/ui'; import { useSoapboxConfig } from 'soapbox/hooks'; const messages = defineMessages({ diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index c8302436b5..8847f22e64 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -49,6 +49,8 @@ import ErrorBoundary from '../components/error-boundary'; import UI from '../features/ui'; import { store } from '../store'; +const RTL_LOCALES = ['ar', 'ckb', 'fa', 'he']; + // Configure global functions for developers createGlobals(store); @@ -277,7 +279,7 @@ const SoapboxHead: React.FC = ({ children }) => { <> - + {themeCss && } {darkMode && } diff --git a/app/soapbox/features/account-gallery/components/media-item.tsx b/app/soapbox/features/account-gallery/components/media-item.tsx index a2a171bfcc..4242094b55 100644 --- a/app/soapbox/features/account-gallery/components/media-item.tsx +++ b/app/soapbox/features/account-gallery/components/media-item.tsx @@ -74,6 +74,7 @@ const MediaItem: React.FC = ({ attachment, displayWidth, onOpenMedia src={attachment.preview_url} alt={attachment.description} style={{ objectPosition: `${x}% ${y}%` }} + className='w-full h-full rounded-lg overflow-hidden' /> ); } else if (['gifv', 'video'].indexOf(attachment.type) !== -1) { diff --git a/app/soapbox/features/account/components/header.tsx b/app/soapbox/features/account/components/header.tsx index 7c41896e71..fae9baf698 100644 --- a/app/soapbox/features/account/components/header.tsx +++ b/app/soapbox/features/account/components/header.tsx @@ -19,7 +19,7 @@ import { getSettings } from 'soapbox/actions/settings'; import snackbar from 'soapbox/actions/snackbar'; import Badge from 'soapbox/components/badge'; import StillImage from 'soapbox/components/still-image'; -import { HStack, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuLink, MenuDivider, Avatar } from 'soapbox/components/ui'; +import { Avatar, HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList } from 'soapbox/components/ui'; import SvgIcon from 'soapbox/components/ui/icon/svg-icon'; import MovedNote from 'soapbox/features/account-timeline/components/moved-note'; import ActionButton from 'soapbox/features/ui/components/action-button'; @@ -71,6 +71,7 @@ const messages = defineMessages({ userEndorsed: { id: 'account.endorse.success', defaultMessage: 'You are now featuring @{acct} on your profile' }, userUnendorsed: { id: 'account.unendorse.success', defaultMessage: 'You are no longer featuring @{acct}' }, profileExternal: { id: 'account.profile_external', defaultMessage: 'View profile on {domain}' }, + header: { id: 'account.header.alt', defaultMessage: 'Profile header' }, }); interface IHeader { @@ -108,13 +109,13 @@ const Header: React.FC = ({ account }) => {
-
+
-
+
); @@ -573,13 +574,12 @@ const Header: React.FC = ({ account }) => { )}
-
+
{account.header && ( )} @@ -593,19 +593,19 @@ const Header: React.FC = ({ account }) => {
-
+
-
+ {ownAccount && ( @@ -629,13 +629,13 @@ const Header: React.FC = ({ account }) => { return ( -
+ {menuItem.icon && ( - + )}
{menuItem.text}
-
+
); } @@ -648,9 +648,9 @@ const Header: React.FC = ({ account }) => { {renderMessageButton()} -
+
-
+
); diff --git a/app/soapbox/features/ads/components/ad.tsx b/app/soapbox/features/ads/components/ad.tsx index 1112c788ce..37b6ce92b1 100644 --- a/app/soapbox/features/ads/components/ad.tsx +++ b/app/soapbox/features/ads/components/ad.tsx @@ -2,8 +2,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import React, { useState, useEffect, useRef } from 'react'; import { FormattedMessage } from 'react-intl'; -import { Stack, HStack, Card, Avatar, Text, Icon } from 'soapbox/components/ui'; -import IconButton from 'soapbox/components/ui/icon-button/icon-button'; +import { Avatar, Card, HStack, Icon, IconButton, Stack, Text } from 'soapbox/components/ui'; import StatusCard from 'soapbox/features/status/components/card'; import { useAppSelector } from 'soapbox/hooks'; import { AdKeys } from 'soapbox/queries/ads'; diff --git a/app/soapbox/features/aliases/components/account.tsx b/app/soapbox/features/aliases/components/account.tsx index f76431ba8c..1687b85e6d 100644 --- a/app/soapbox/features/aliases/components/account.tsx +++ b/app/soapbox/features/aliases/components/account.tsx @@ -2,9 +2,9 @@ import React, { useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { addToAliases } from 'soapbox/actions/aliases'; -import Avatar from 'soapbox/components/avatar'; -import DisplayName from 'soapbox/components/display-name'; +import AccountComponent from 'soapbox/components/account'; import IconButton from 'soapbox/components/icon-button'; +import { HStack } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; import { getFeatures } from 'soapbox/utils/features'; @@ -47,23 +47,17 @@ const Account: React.FC = ({ accountId, aliases }) => { if (!added && accountId !== me) { button = ( -
- -
+ ); } return ( -
-
-
-
- -
- - {button} + +
+
-
+ {button} + ); }; diff --git a/app/soapbox/features/aliases/components/search.tsx b/app/soapbox/features/aliases/components/search.tsx index b84d90aa47..f64c4a3323 100644 --- a/app/soapbox/features/aliases/components/search.tsx +++ b/app/soapbox/features/aliases/components/search.tsx @@ -53,7 +53,7 @@ const Search: React.FC = () => { placeholder={intl.formatMessage(messages.search)} /> -
+
diff --git a/app/soapbox/features/auth-login/components/login-page.tsx b/app/soapbox/features/auth-login/components/login-page.tsx index c137d0165d..609af73a46 100644 --- a/app/soapbox/features/auth-login/components/login-page.tsx +++ b/app/soapbox/features/auth-login/components/login-page.tsx @@ -26,32 +26,35 @@ const LoginPage = () => { const [mfaToken, setMfaToken] = useState(token || ''); const [shouldRedirect, setShouldRedirect] = useState(false); - const getFormData = (form: HTMLFormElement) => { - return Object.fromEntries( + const getFormData = (form: HTMLFormElement) => + Object.fromEntries( Array.from(form).map((i: any) => [i.name, i.value]), ); - }; const handleSubmit: React.FormEventHandler = (event) => { const { username, password } = getFormData(event.target as HTMLFormElement); - dispatch(logIn(username, password)).then(({ access_token }) => { - return dispatch(verifyCredentials(access_token as string)) - // Refetch the instance for authenticated fetch - .then(() => dispatch(fetchInstance() as any)); - }).then((account: { id: string }) => { - dispatch(closeModal()); - setShouldRedirect(true); - if (typeof me === 'string') { - dispatch(switchAccount(account.id)); - } - }).catch((error: AxiosError) => { - const data: any = error.response?.data; - if (data?.error === 'mfa_required') { - setMfaAuthNeeded(true); - setMfaToken(data.mfa_token); - } - setIsLoading(false); - }); + dispatch(logIn(username, password)) + .then(({ access_token }) => dispatch(verifyCredentials(access_token as string))) + // Refetch the instance for authenticated fetch + .then(async (account) => { + await dispatch(fetchInstance()); + return account; + }) + .then((account: { id: string }) => { + dispatch(closeModal()); + if (typeof me === 'string') { + dispatch(switchAccount(account.id)); + } else { + setShouldRedirect(true); + } + }).catch((error: AxiosError) => { + const data: any = error.response?.data; + if (data?.error === 'mfa_required') { + setMfaAuthNeeded(true); + setMfaToken(data.mfa_token); + } + setIsLoading(false); + }); setIsLoading(true); event.preventDefault(); }; diff --git a/app/soapbox/features/auth-login/components/password-reset-confirm.tsx b/app/soapbox/features/auth-login/components/password-reset-confirm.tsx index 2a55c54836..f0a4d1c318 100644 --- a/app/soapbox/features/auth-login/components/password-reset-confirm.tsx +++ b/app/soapbox/features/auth-login/components/password-reset-confirm.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Redirect } from 'react-router-dom'; @@ -11,6 +11,7 @@ const token = new URLSearchParams(window.location.search).get('reset_password_to const messages = defineMessages({ resetPasswordFail: { id: 'reset_password.fail', defaultMessage: 'Expired token, please try again.' }, + passwordPlaceholder: { id: 'reset_password.password.placeholder', defaultMessage: 'Placeholder' }, }); const Statuses = { @@ -66,11 +67,11 @@ const PasswordResetConfirm = () => {
- + } errors={renderErrors()}> diff --git a/app/soapbox/features/compose/components/compose-form.tsx b/app/soapbox/features/compose/components/compose-form.tsx index 78a9cd11ac..1267c87539 100644 --- a/app/soapbox/features/compose/components/compose-form.tsx +++ b/app/soapbox/features/compose/components/compose-form.tsx @@ -16,7 +16,7 @@ import { import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest-input'; import AutosuggestTextarea from 'soapbox/components/autosuggest-textarea'; import Icon from 'soapbox/components/icon'; -import { Button, Stack } from 'soapbox/components/ui'; +import { Button, HStack, Stack } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector, useCompose, useFeatures, usePrevious } from 'soapbox/hooks'; import { isMobile } from 'soapbox/is-mobile'; @@ -221,7 +221,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab }, [focusDate]); const renderButtons = useCallback(() => ( -
+ {features.media && } {features.polls && } @@ -229,7 +229,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab {features.scheduledStatuses && } {features.spoilers && } {features.richText && } -
+ ), [features, id]); const condensed = shouldCondense && !composeFocused && isEmpty() && !isUploading; @@ -335,16 +335,18 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab > {renderButtons()} -
+ {maxTootChars && ( -
+ -
+
)}
+ + {/* + */}
); diff --git a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx index a7a4dff28a..834b556f25 100644 --- a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx +++ b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx @@ -4,7 +4,7 @@ import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { buildCustomEmojis } from '../../../emoji/emoji'; +import { buildCustomEmojis, categoriesFromEmojis } from '../../../emoji/emoji'; import { EmojiPicker } from './emoji-picker-dropdown'; import ModifierPicker from './modifier-picker'; @@ -14,19 +14,6 @@ import type { Emoji } from 'soapbox/components/autosuggest-emoji'; const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png'); const listenerOptions = supportsPassiveEvents ? { passive: true } : false; -const categoriesSort = [ - 'recent', - 'custom', - 'people', - 'nature', - 'foods', - 'activity', - 'places', - 'objects', - 'symbols', - 'flags', -]; - const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, @@ -71,6 +58,20 @@ const EmojiPickerMenu: React.FC = ({ const [modifierOpen, setModifierOpen] = useState(false); + const categoriesSort = [ + 'recent', + 'people', + 'nature', + 'foods', + 'activity', + 'places', + 'objects', + 'symbols', + 'flags', + ]; + + categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(customEmojis) as Set).sort()); + const handleDocumentClick = useCallback(e => { if (node.current && !node.current.contains(e.target)) { onClose(); diff --git a/app/soapbox/features/compose/components/polls/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx index f080ae1fa7..77f47bdfce 100644 --- a/app/soapbox/features/compose/components/polls/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -169,7 +169,7 @@ const PollForm: React.FC = ({ composeId }) => { -