Merge remote-tracking branch 'origin/develop' into chats
This commit is contained in:
commit
50f5e2af38
59 changed files with 322 additions and 247 deletions
|
@ -4,7 +4,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
import IconButton from 'soapbox/components/icon-button';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
|
||||
import { DatePicker } from 'soapbox/features/ui/util/async-components';
|
||||
import { useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||
import { useInstance, useFeatures } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
birthdayPlaceholder: { id: 'edit_profile.fields.birthday_placeholder', defaultMessage: 'Your birthday' },
|
||||
|
@ -23,9 +23,10 @@ interface IBirthdayInput {
|
|||
const BirthdayInput: React.FC<IBirthdayInput> = ({ value, onChange, required }) => {
|
||||
const intl = useIntl();
|
||||
const features = useFeatures();
|
||||
const instance = useInstance();
|
||||
|
||||
const supportsBirthdays = features.birthdays;
|
||||
const minAge = useAppSelector((state) => state.instance.pleroma.getIn(['metadata', 'birthday_min_age'])) as number;
|
||||
const minAge = instance.pleroma.getIn(['metadata', 'birthday_min_age']) as number;
|
||||
|
||||
const maxDate = useMemo(() => {
|
||||
if (!supportsBirthdays) return null;
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { useState } from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Banner, Button, HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
const acceptedGdpr = !!localStorage.getItem('soapbox:gdpr');
|
||||
|
||||
|
@ -13,9 +13,9 @@ const GdprBanner: React.FC = () => {
|
|||
const [shown, setShown] = useState<boolean>(acceptedGdpr);
|
||||
const [slideout, setSlideout] = useState(false);
|
||||
|
||||
const instance = useInstance();
|
||||
const soapbox = useSoapboxConfig();
|
||||
const isLoggedIn = useAppSelector(state => !!state.me);
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
|
||||
const handleAccept = () => {
|
||||
localStorage.setItem('soapbox:gdpr', 'true');
|
||||
|
@ -34,14 +34,14 @@ const GdprBanner: React.FC = () => {
|
|||
<div className='flex flex-col space-y-4 lg:space-y-0 lg:space-x-4 rtl:space-x-reverse lg:flex-row lg:items-center lg:justify-between'>
|
||||
<Stack space={2}>
|
||||
<Text size='xl' weight='bold'>
|
||||
<FormattedMessage id='gdpr.title' defaultMessage='{siteTitle} uses cookies' values={{ siteTitle }} />
|
||||
<FormattedMessage id='gdpr.title' defaultMessage='{siteTitle} uses cookies' values={{ siteTitle: instance.title }} />
|
||||
</Text>
|
||||
|
||||
<Text weight='medium' className='opacity-60'>
|
||||
<FormattedMessage
|
||||
id='gdpr.message'
|
||||
defaultMessage="{siteTitle} uses session cookies, which are essential to the website's functioning."
|
||||
values={{ siteTitle }}
|
||||
values={{ siteTitle: instance.title }}
|
||||
/>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Helmet as ReactHelmet } from 'react-helmet';
|
||||
|
||||
import { useAppSelector, useSettings } from 'soapbox/hooks';
|
||||
import { useAppSelector, useInstance, useSettings } from 'soapbox/hooks';
|
||||
import { RootState } from 'soapbox/store';
|
||||
import FaviconService from 'soapbox/utils/favicon-service';
|
||||
|
||||
|
@ -16,7 +16,7 @@ const getNotifTotals = (state: RootState): number => {
|
|||
};
|
||||
|
||||
const Helmet: React.FC = ({ children }) => {
|
||||
const title = useAppSelector((state) => state.instance.title);
|
||||
const instance = useInstance();
|
||||
const unreadCount = useAppSelector((state) => getNotifTotals(state));
|
||||
const demetricator = useSettings().get('demetricator');
|
||||
|
||||
|
@ -40,8 +40,8 @@ const Helmet: React.FC = ({ children }) => {
|
|||
|
||||
return (
|
||||
<ReactHelmet
|
||||
titleTemplate={addCounter(`%s | ${title}`)}
|
||||
defaultTitle={addCounter(title)}
|
||||
titleTemplate={addCounter(`%s | ${instance.title}`)}
|
||||
defaultTitle={addCounter(instance.title)}
|
||||
defer={false}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
||||
import { useStatContext } from 'soapbox/contexts/stat-context';
|
||||
import ComposeButton from 'soapbox/features/ui/components/compose-button';
|
||||
import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { useAppSelector, useFeatures, useOwnAccount, useSettings } from 'soapbox/hooks';
|
||||
|
||||
import SidebarNavigationLink from './sidebar-navigation-link';
|
||||
|
||||
|
@ -24,15 +22,13 @@ const SidebarNavigation = () => {
|
|||
const intl = useIntl();
|
||||
const { unreadChatsCount } = useStatContext();
|
||||
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const settings = useAppSelector((state) => getSettings(state));
|
||||
const features = useFeatures();
|
||||
const settings = useSettings();
|
||||
const account = useOwnAccount();
|
||||
const notificationCount = useAppSelector((state) => state.notifications.unread);
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
|
||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
||||
|
||||
const features = getFeatures(instance);
|
||||
|
||||
const makeMenu = (): Menu => {
|
||||
const menu: Menu = [];
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ describe('<SensitiveContentOverlay />', () => {
|
|||
expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Sensitive content');
|
||||
});
|
||||
|
||||
it('does not allow user to delete the status', () => {
|
||||
render(<SensitiveContentOverlay status={status} />);
|
||||
expect(screen.queryAllByTestId('icon-button')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('can be toggled', () => {
|
||||
render(<SensitiveContentOverlay status={status} />);
|
||||
|
||||
|
@ -43,6 +48,11 @@ describe('<SensitiveContentOverlay />', () => {
|
|||
expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Content Under Review');
|
||||
});
|
||||
|
||||
it('allows the user to delete the status', () => {
|
||||
render(<SensitiveContentOverlay status={status} />);
|
||||
expect(screen.getByTestId('icon-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can be toggled', () => {
|
||||
render(<SensitiveContentOverlay status={status} />);
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import classNames from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { deleteStatus } from 'soapbox/actions/statuses';
|
||||
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
||||
import { useAppDispatch, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { defaultMediaVisibility } from 'soapbox/utils/status';
|
||||
|
||||
import { Button, HStack, Text } from '../ui';
|
||||
|
@ -10,6 +13,10 @@ import { Button, HStack, Text } from '../ui';
|
|||
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteHeading: { id: 'confirmations.delete.heading', defaultMessage: 'Delete post' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' },
|
||||
hide: { id: 'moderation_overlay.hide', defaultMessage: 'Hide content' },
|
||||
sensitiveTitle: { id: 'status.sensitive_warning', defaultMessage: 'Sensitive content' },
|
||||
underReviewTitle: { id: 'moderation_overlay.title', defaultMessage: 'Content Under Review' },
|
||||
|
@ -27,15 +34,17 @@ interface ISensitiveContentOverlay {
|
|||
|
||||
const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveContentOverlay>((props, ref) => {
|
||||
const { onToggleVisibility, status } = props;
|
||||
const isUnderReview = status.visibility === 'self';
|
||||
|
||||
const settings = useSettings();
|
||||
const displayMedia = settings.get('displayMedia') as string;
|
||||
|
||||
const account = useOwnAccount();
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const settings = useSettings();
|
||||
const { links } = useSoapboxConfig();
|
||||
|
||||
const isUnderReview = status.visibility === 'self';
|
||||
const isOwnStatus = status.getIn(['account', 'id']) === account?.id;
|
||||
const displayMedia = settings.get('displayMedia') as string;
|
||||
|
||||
const [visible, setVisible] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
||||
|
||||
const toggleVisibility = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
|
@ -48,6 +57,32 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
|
|||
}
|
||||
};
|
||||
|
||||
const handleDeleteStatus = () => {
|
||||
const deleteModal = settings.get('deleteModal');
|
||||
if (!deleteModal) {
|
||||
dispatch(deleteStatus(status.id, false));
|
||||
} else {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
icon: require('@tabler/icons/trash.svg'),
|
||||
heading: intl.formatMessage(messages.deleteHeading),
|
||||
message: intl.formatMessage(messages.deleteMessage),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => dispatch(deleteStatus(status.id, false)),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const menu = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
text: intl.formatMessage(messages.delete),
|
||||
action: handleDeleteStatus,
|
||||
icon: require('@tabler/icons/trash.svg'),
|
||||
destructive: true,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof props.visible !== 'undefined') {
|
||||
setVisible(!!props.visible);
|
||||
|
@ -122,6 +157,13 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
|
|||
>
|
||||
{intl.formatMessage(messages.show)}
|
||||
</Button>
|
||||
|
||||
{(isUnderReview && isOwnStatus) ? (
|
||||
<DropdownMenu
|
||||
items={menu}
|
||||
src={require('@tabler/icons/dots.svg')}
|
||||
/>
|
||||
) : null}
|
||||
</HStack>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -3,8 +3,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
import ThumbNavigationLink from 'soapbox/components/thumb-navigation-link';
|
||||
import { useStatContext } from 'soapbox/contexts/stat-context';
|
||||
import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
|
||||
const ThumbNavigation: React.FC = (): JSX.Element => {
|
||||
const account = useOwnAccount();
|
||||
|
@ -12,7 +11,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
|
|||
|
||||
const notificationCount = useAppSelector((state) => state.notifications.unread);
|
||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
||||
const features = getFeatures(useAppSelector((state) => state.instance));
|
||||
const features = useFeatures();
|
||||
|
||||
/** Conditionally render the supported messages link */
|
||||
const renderMessagesLink = (): React.ReactNode => {
|
||||
|
|
|
@ -51,7 +51,7 @@ const families = {
|
|||
};
|
||||
|
||||
export type Sizes = keyof typeof sizes
|
||||
type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label'
|
||||
type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'div'
|
||||
type Directions = 'ltr' | 'rtl'
|
||||
|
||||
interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'dangerouslySetInnerHTML' | 'tabIndex' | 'lang'> {
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
useSettings,
|
||||
useTheme,
|
||||
useLocale,
|
||||
useInstance,
|
||||
} from 'soapbox/hooks';
|
||||
import MESSAGES from 'soapbox/locales/messages';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
|
@ -86,7 +87,7 @@ const SoapboxMount = () => {
|
|||
useCachedLocationHandler();
|
||||
|
||||
const me = useAppSelector(state => state.me);
|
||||
const instance = useAppSelector(state => state.instance);
|
||||
const instance = useInstance();
|
||||
const account = useOwnAccount();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const features = useFeatures();
|
||||
|
|
|
@ -11,9 +11,8 @@ import { expandAccountMediaTimeline } from 'soapbox/actions/timelines';
|
|||
import LoadMore from 'soapbox/components/load-more';
|
||||
import MissingIndicator from 'soapbox/components/missing-indicator';
|
||||
import { Column, Spinner } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||
import { getAccountGallery, findAccountByUsername } from 'soapbox/selectors';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import MediaItem from './components/media-item';
|
||||
|
||||
|
@ -38,11 +37,11 @@ const LoadMoreMedia: React.FC<ILoadMoreMedia> = ({ maxId, onLoadMore }) => {
|
|||
const AccountGallery = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { username } = useParams<{ username: string }>();
|
||||
const features = useFeatures();
|
||||
|
||||
const { accountId, unavailable, accountUsername } = useAppSelector((state) => {
|
||||
const me = state.me;
|
||||
const accountFetchError = (state.accounts.get(-1)?.username || '').toLowerCase() === username.toLowerCase();
|
||||
const features = getFeatures(state.instance);
|
||||
|
||||
let accountId: string | -1 | null = -1;
|
||||
let accountUsername = username;
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
RadioGroup,
|
||||
RadioItem,
|
||||
} from 'soapbox/features/forms';
|
||||
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||
|
||||
import type { Instance } from 'soapbox/types/entities';
|
||||
|
||||
|
@ -42,8 +42,9 @@ const modeFromInstance = (instance: Instance): RegistrationMode => {
|
|||
const RegistrationModePicker: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
|
||||
const mode = useAppSelector(state => modeFromInstance(state.instance));
|
||||
const mode = modeFromInstance(instance);
|
||||
|
||||
const onChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||
const config = generateConfig(e.target.value as RegistrationMode);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
|
|||
|
||||
import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email-list';
|
||||
import { Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useOwnAccount, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
import sourceCode from 'soapbox/utils/code';
|
||||
import { parseVersion } from 'soapbox/utils/features';
|
||||
import { isNumber } from 'soapbox/utils/numbers';
|
||||
|
@ -27,7 +27,7 @@ const download = (response: AxiosResponse, filename: string) => {
|
|||
|
||||
const Dashboard: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useAppSelector(state => state.instance);
|
||||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
const account = useOwnAccount();
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
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 { useInstance } from 'soapbox/hooks';
|
||||
import { AdKeys } from 'soapbox/queries/ads';
|
||||
|
||||
import type { Ad as AdEntity } from 'soapbox/types/soapbox';
|
||||
|
@ -16,7 +16,7 @@ interface IAd {
|
|||
/** Displays an ad in sponsored post format. */
|
||||
const Ad: React.FC<IAd> = ({ ad }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const instance = useAppSelector(state => state.instance);
|
||||
const instance = useInstance();
|
||||
|
||||
const timer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const infobox = useRef<HTMLDivElement>(null);
|
||||
|
|
|
@ -5,9 +5,8 @@ import { addToAliases } from 'soapbox/actions/aliases';
|
|||
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 { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
|
||||
|
@ -23,21 +22,19 @@ interface IAccount {
|
|||
const Account: React.FC<IAccount> = ({ accountId, aliases }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
||||
const getAccount = useCallback(makeGetAccount(), []);
|
||||
|
||||
const account = useAppSelector((state) => getAccount(state, accountId));
|
||||
const added = useAppSelector((state) => {
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
const me = useAppSelector((state) => state.me);
|
||||
|
||||
const added = useAppSelector((state) => {
|
||||
const account = getAccount(state, accountId);
|
||||
const apId = account?.pleroma.get('ap_id');
|
||||
const name = features.accountMoving ? account?.acct : apId;
|
||||
|
||||
return aliases.includes(name);
|
||||
});
|
||||
const me = useAppSelector((state) => state.me);
|
||||
|
||||
const handleOnAdd = () => dispatch(addToAliases(account!));
|
||||
|
||||
|
|
|
@ -6,9 +6,7 @@ import { fetchAliases, removeFromAliases } from 'soapbox/actions/aliases';
|
|||
import Icon from 'soapbox/components/icon';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { CardHeader, CardTitle, Column, HStack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
|
||||
import Account from './components/account';
|
||||
import Search from './components/search';
|
||||
|
@ -22,22 +20,21 @@ const messages = defineMessages({
|
|||
delete: { id: 'column.aliases.delete', defaultMessage: 'Delete' },
|
||||
});
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const Aliases = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const account = useOwnAccount();
|
||||
|
||||
const aliases = useAppSelector((state) => {
|
||||
const me = state.me as string;
|
||||
const account = getAccount(state, me);
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (features.accountMoving) return state.aliases.aliases.items;
|
||||
return account!.pleroma.get('also_known_as');
|
||||
if (features.accountMoving) {
|
||||
return state.aliases.aliases.items;
|
||||
} else {
|
||||
return account!.pleroma.get('also_known_as');
|
||||
}
|
||||
}) as ImmutableList<string>;
|
||||
|
||||
const searchAccountIds = useAppSelector((state) => state.aliases.suggestions.items);
|
||||
const loaded = useAppSelector((state) => state.aliases.suggestions.loaded);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Link, Redirect, Route, Switch, useHistory, useLocation } from 'react-ro
|
|||
|
||||
import LandingGradient from 'soapbox/components/landing-gradient';
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount } from 'soapbox/hooks';
|
||||
import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount, useInstance } from 'soapbox/hooks';
|
||||
|
||||
import { Button, Card, CardBody } from '../../components/ui';
|
||||
import LoginPage from '../auth-login/components/login-page';
|
||||
|
@ -27,12 +27,11 @@ const AuthLayout = () => {
|
|||
const { search } = useLocation();
|
||||
|
||||
const account = useOwnAccount();
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
|
||||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
|
||||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
const isOpen = features.accountCreation && instance.registrations;
|
||||
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
|
||||
const isLoginPage = history.location.pathname === '/login';
|
||||
|
@ -47,7 +46,7 @@ const AuthLayout = () => {
|
|||
<header className='flex justify-between relative py-12 px-2 mb-auto'>
|
||||
<div className='relative z-0 flex-1 px-2 lg:flex lg:items-center lg:justify-center lg:absolute lg:inset-0'>
|
||||
<Link to='/' className='cursor-pointer'>
|
||||
<SiteLogo alt={siteTitle} className='h-7' />
|
||||
<SiteLogo alt={instance.title} className='h-7' />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Card, HStack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
|
||||
import ConsumerButton from './consumer-button';
|
||||
|
||||
|
@ -12,7 +12,8 @@ interface IConsumersList {
|
|||
|
||||
/** Displays OAuth consumers to log in with. */
|
||||
const ConsumersList: React.FC<IConsumersList> = () => {
|
||||
const providers = useAppSelector(state => ImmutableList<string>(state.instance.pleroma.get('oauth_consumer_strategies')));
|
||||
const instance = useInstance();
|
||||
const providers = ImmutableList<string>(instance.pleroma.get('oauth_consumer_strategies'));
|
||||
|
||||
if (providers.size > 0) {
|
||||
return (
|
||||
|
|
|
@ -12,7 +12,7 @@ import { openModal } from 'soapbox/actions/modals';
|
|||
import BirthdayInput from 'soapbox/components/birthday-input';
|
||||
import { Checkbox, Form, FormGroup, FormActions, Button, Input, Textarea } from 'soapbox/components/ui';
|
||||
import CaptchaField from 'soapbox/features/auth-login/components/captcha';
|
||||
import { useAppSelector, useAppDispatch, useSettings, useFeatures } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useSettings, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
username: { id: 'registration.fields.username_placeholder', defaultMessage: 'Username' },
|
||||
|
@ -42,7 +42,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
|
|||
|
||||
const settings = useSettings();
|
||||
const features = useFeatures();
|
||||
const instance = useAppSelector(state => state.instance);
|
||||
const instance = useInstance();
|
||||
|
||||
const locale = settings.get('locale');
|
||||
const needsConfirmation = !!instance.pleroma.getIn(['metadata', 'account_activation_required']);
|
||||
|
|
|
@ -17,7 +17,7 @@ import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest
|
|||
import AutosuggestTextarea from 'soapbox/components/autosuggest-textarea';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { Button, HStack, Stack } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useCompose, useFeatures, usePrevious } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useCompose, useFeatures, useInstance, usePrevious } from 'soapbox/hooks';
|
||||
import { isMobile } from 'soapbox/is-mobile';
|
||||
|
||||
import QuotedStatusContainer from '../containers/quoted-status-container';
|
||||
|
@ -67,11 +67,12 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
|
|||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { configuration } = useInstance();
|
||||
|
||||
const compose = useCompose(id);
|
||||
const showSearch = useAppSelector((state) => state.search.submitted && !state.search.hidden);
|
||||
const isModalOpen = useAppSelector((state) => !!(state.modals.size && state.modals.last()!.modalType === 'COMPOSE'));
|
||||
const maxTootChars = useAppSelector((state) => state.instance.getIn(['configuration', 'statuses', 'max_characters'])) as number;
|
||||
const maxTootChars = configuration.getIn(['statuses', 'max_characters']) as number;
|
||||
const scheduledStatusCount = useAppSelector((state) => state.get('scheduled_statuses').size);
|
||||
const features = useFeatures();
|
||||
|
||||
|
|
|
@ -4,10 +4,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { addPollOption, changePollOption, changePollSettings, clearComposeSuggestions, fetchComposeSuggestions, removePoll, removePollOption, selectComposeSuggestion } from 'soapbox/actions/compose';
|
||||
import AutosuggestInput from 'soapbox/components/autosuggest-input';
|
||||
import { Button, Divider, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useCompose } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useCompose, useInstance } from 'soapbox/hooks';
|
||||
|
||||
import DurationSelector from './duration-selector';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -110,16 +111,17 @@ interface IPollForm {
|
|||
const PollForm: React.FC<IPollForm> = ({ composeId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const { configuration } = useInstance();
|
||||
|
||||
const compose = useCompose(composeId);
|
||||
|
||||
const pollLimits = useAppSelector((state) => state.instance.getIn(['configuration', 'polls']) as any);
|
||||
const pollLimits = configuration.get('polls') as ImmutableMap<string, number>;
|
||||
const options = compose.poll?.options;
|
||||
const expiresIn = compose.poll?.expires_in;
|
||||
const isMultiple = compose.poll?.multiple;
|
||||
|
||||
const maxOptions = pollLimits.get('max_options');
|
||||
const maxOptionChars = pollLimits.get('max_characters_per_option');
|
||||
const maxOptions = pollLimits.get('max_options') as number;
|
||||
const maxOptionChars = pollLimits.get('max_characters_per_option') as number;
|
||||
|
||||
const onRemoveOption = (index: number) => dispatch(removePollOption(composeId, index));
|
||||
const onChangeOption = (index: number, title: string) => dispatch(changePollOption(composeId, index, title));
|
||||
|
|
|
@ -3,10 +3,9 @@ import { FormattedList, FormattedMessage } from 'react-intl';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { useAppSelector, useCompose } from 'soapbox/hooks';
|
||||
import { useAppSelector, useCompose, useFeatures } from 'soapbox/hooks';
|
||||
import { statusToMentionsAccountIdsArray } from 'soapbox/reducers/compose';
|
||||
import { makeGetStatus } from 'soapbox/selectors';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||
|
||||
|
@ -16,18 +15,15 @@ interface IReplyMentions {
|
|||
|
||||
const ReplyMentions: React.FC<IReplyMentions> = ({ composeId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
|
||||
const features = useFeatures();
|
||||
const compose = useCompose(composeId);
|
||||
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const status = useAppSelector<StatusEntity | null>(state => getStatus(state, { id: compose.in_reply_to! }));
|
||||
const to = compose.to;
|
||||
const account = useAppSelector((state) => state.accounts.get(state.me));
|
||||
|
||||
const { explicitAddressing } = getFeatures(instance);
|
||||
|
||||
if (!explicitAddressing || !status || !to) {
|
||||
if (!features.explicitAddressing || !status || !to) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { useRef } from 'react';
|
|||
import { defineMessages, IntlShape, useIntl } from 'react-intl';
|
||||
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
|
||||
|
@ -29,9 +29,10 @@ const UploadButton: React.FC<IUploadButton> = ({
|
|||
resetFileKey,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { configuration } = useInstance();
|
||||
|
||||
const fileElement = useRef<HTMLInputElement>(null);
|
||||
const attachmentTypes = useAppSelector(state => state.instance.configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList<string>);
|
||||
const attachmentTypes = configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList<string>;
|
||||
|
||||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
if (e.target.files?.length) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { openModal } from 'soapbox/actions/modals';
|
|||
import Blurhash from 'soapbox/components/blurhash';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import IconButton from 'soapbox/components/icon-button';
|
||||
import { useAppDispatch, useAppSelector, useCompose } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useCompose, useInstance } from 'soapbox/hooks';
|
||||
|
||||
import Motion from '../../ui/util/optional-motion';
|
||||
|
||||
|
@ -70,9 +70,9 @@ const Upload: React.FC<IUpload> = ({ composeId, id }) => {
|
|||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const { description_limit: descriptionLimit } = useInstance();
|
||||
|
||||
const media = useCompose(composeId).media_attachments.find(item => item.get('id') === id)!;
|
||||
const descriptionLimit = useAppSelector((state) => state.instance.get('description_limit'));
|
||||
const media = useCompose(composeId).media_attachments.find(item => item.id === id)!;
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { Text, Widget } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
import SiteWallet from './site-wallet';
|
||||
|
||||
|
@ -18,9 +18,9 @@ interface ICryptoDonatePanel {
|
|||
const CryptoDonatePanel: React.FC<ICryptoDonatePanel> = ({ limit = 3 }): JSX.Element | null => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const instance = useInstance();
|
||||
|
||||
const addresses = useSoapboxConfig().get('cryptoAddresses');
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
|
||||
if (limit === 0 || addresses.size === 0) {
|
||||
return null;
|
||||
|
@ -40,7 +40,7 @@ const CryptoDonatePanel: React.FC<ICryptoDonatePanel> = ({ limit = 3 }): JSX.Ele
|
|||
<FormattedMessage
|
||||
id='crypto_donate_panel.intro.message'
|
||||
defaultMessage='{siteTitle} accepts cryptocurrency donations to fund our service. Thank you for your support!'
|
||||
values={{ siteTitle }}
|
||||
values={{ siteTitle: instance.title }}
|
||||
/>
|
||||
</Text>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Accordion, Column, Stack } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
|
||||
import SiteWallet from './components/site-wallet';
|
||||
|
||||
|
@ -11,9 +11,10 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
const CryptoDonate: React.FC = (): JSX.Element => {
|
||||
const [explanationBoxExpanded, toggleExplanationBox] = useState(true);
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
const intl = useIntl();
|
||||
const instance = useInstance();
|
||||
|
||||
const [explanationBoxExpanded, toggleExplanationBox] = useState(true);
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} withHeader>
|
||||
|
@ -26,7 +27,7 @@ const CryptoDonate: React.FC = (): JSX.Element => {
|
|||
<FormattedMessage
|
||||
id='crypto_donate.explanation_box.message'
|
||||
defaultMessage='{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!'
|
||||
values={{ siteTitle }}
|
||||
values={{ siteTitle: instance.title }}
|
||||
/>
|
||||
</Accordion>
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ 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 } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { useAppSelector, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
|
||||
import AccountCard from './components/account-card';
|
||||
|
||||
|
@ -25,11 +24,11 @@ const Directory = () => {
|
|||
const dispatch = useDispatch();
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
|
||||
const accountIds = useAppSelector((state) => state.user_lists.directory.items);
|
||||
const isLoading = useAppSelector((state) => state.user_lists.directory.isLoading);
|
||||
const title = useAppSelector((state) => state.instance.get('title'));
|
||||
const features = useAppSelector((state) => getFeatures(state.instance));
|
||||
|
||||
const [order, setOrder] = useState(params.get('order') || 'active');
|
||||
const [local, setLocal] = useState(!!params.get('local'));
|
||||
|
@ -71,7 +70,7 @@ const Directory = () => {
|
|||
<fieldset className='mt-3'>
|
||||
<legend className='sr-only'>Fediverse filter</legend>
|
||||
<div className='space-y-2'>
|
||||
<RadioButton name='local' value='1' label={intl.formatMessage(messages.local, { domain: title })} checked={local} onChange={handleChangeLocal} />
|
||||
<RadioButton name='local' value='1' label={intl.formatMessage(messages.local, { domain: instance.title })} checked={local} onChange={handleChangeLocal} />
|
||||
<RadioButton name='local' value='0' label={intl.formatMessage(messages.federated)} checked={!local} onChange={handleChangeLocal} />
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
Textarea,
|
||||
Toggle,
|
||||
} from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useOwnAccount, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
import { normalizeAccount } from 'soapbox/normalizers';
|
||||
import resizeImage from 'soapbox/utils/resize-image';
|
||||
|
||||
|
@ -171,10 +171,11 @@ const ProfileField: StreamfieldComponent<AccountCredentialsField> = ({ value, on
|
|||
const EditProfile: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
|
||||
const account = useOwnAccount();
|
||||
const features = useFeatures();
|
||||
const maxFields = useAppSelector(state => state.instance.pleroma.getIn(['metadata', 'fields_limits', 'max_fields'], 4) as number);
|
||||
const maxFields = instance.pleroma.getIn(['metadata', 'fields_limits', 'max_fields'], 4) as number;
|
||||
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<AccountCredentials>({});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
|
@ -38,7 +38,7 @@ interface IInstanceRestrictions {
|
|||
}
|
||||
|
||||
const InstanceRestrictions: React.FC<IInstanceRestrictions> = ({ remoteInstance }) => {
|
||||
const instance = useAppSelector(state => state.instance);
|
||||
const instance = useInstance();
|
||||
|
||||
const renderRestrictions = () => {
|
||||
const items = [];
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Accordion } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
import { makeGetHosts } from 'soapbox/selectors';
|
||||
import { federationRestrictionsDisclosed } from 'soapbox/utils/state';
|
||||
|
||||
|
@ -21,12 +21,12 @@ const messages = defineMessages({
|
|||
notDisclosed: { id: 'federation_restrictions.not_disclosed_message', defaultMessage: '{siteTitle} does not disclose federation restrictions through the API.' },
|
||||
});
|
||||
|
||||
const getHosts = makeGetHosts();
|
||||
|
||||
const FederationRestrictions = () => {
|
||||
const intl = useIntl();
|
||||
const instance = useInstance();
|
||||
|
||||
const getHosts = useCallback(makeGetHosts(), []);
|
||||
|
||||
const siteTitle = useAppSelector((state) => state.instance.get('title'));
|
||||
const hosts = useAppSelector((state) => getHosts(state)) as ImmutableOrderedSet<string>;
|
||||
const disclosed = useAppSelector((state) => federationRestrictionsDisclosed(state));
|
||||
|
||||
|
@ -45,11 +45,11 @@ const FederationRestrictions = () => {
|
|||
expanded={explanationBoxExpanded}
|
||||
onToggle={toggleExplanationBox}
|
||||
>
|
||||
{intl.formatMessage(messages.boxMessage, { siteTitle })}
|
||||
{intl.formatMessage(messages.boxMessage, { siteTitle: instance.title })}
|
||||
</Accordion>
|
||||
|
||||
<div className='pt-4'>
|
||||
<ScrollableList emptyMessage={intl.formatMessage(emptyMessage, { siteTitle })}>
|
||||
<ScrollableList emptyMessage={intl.formatMessage(emptyMessage, { siteTitle: instance.title })}>
|
||||
{hosts.map((host) => <RestrictedInstance key={host} host={host} />)}
|
||||
</ScrollableList>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
|||
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
||||
import { Column, Stack, Text } from 'soapbox/components/ui';
|
||||
import Timeline from 'soapbox/features/ui/components/timeline';
|
||||
import { useAppSelector, useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||
import { useAppSelector, useAppDispatch, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
|
||||
import { clearFeedAccountId } from '../../actions/timelines';
|
||||
|
||||
|
@ -20,12 +20,12 @@ const HomeTimeline: React.FC = () => {
|
|||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const instance = useInstance();
|
||||
|
||||
const polling = useRef<NodeJS.Timer | null>(null);
|
||||
|
||||
const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true);
|
||||
const currentAccountId = useAppSelector(state => state.timelines.get('home')?.feedAccountId as string | undefined);
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
const currentAccountRelationship = useAppSelector(state => currentAccountId ? state.relationships.get(currentAccountId) : null);
|
||||
|
||||
const handleLoadMore = (maxId: string) => {
|
||||
|
@ -104,7 +104,7 @@ const HomeTimeline: React.FC = () => {
|
|||
<FormattedMessage
|
||||
id='empty_column.home.subtitle'
|
||||
defaultMessage='{siteTitle} gets more interesting once you follow other users.'
|
||||
values={{ siteTitle }}
|
||||
values={{ siteTitle: instance.title }}
|
||||
/>
|
||||
</Text>
|
||||
|
||||
|
@ -116,7 +116,7 @@ const HomeTimeline: React.FC = () => {
|
|||
values={{
|
||||
public: (
|
||||
<Link to='/timeline/local' className='text-primary-600 dark:text-primary-400 hover:underline'>
|
||||
<FormattedMessage id='empty_column.home.local_tab' defaultMessage='the {site_title} tab' values={{ site_title: siteTitle }} />
|
||||
<FormattedMessage id='empty_column.home.local_tab' defaultMessage='the {site_title} tab' values={{ site_title: instance.title }} />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
|
|
|
@ -6,7 +6,7 @@ import Markup from 'soapbox/components/markup';
|
|||
import { Button, Card, CardBody, Stack, Text } from 'soapbox/components/ui';
|
||||
import VerificationBadge from 'soapbox/components/verification-badge';
|
||||
import RegistrationForm from 'soapbox/features/auth-login/components/registration-form';
|
||||
import { useAppDispatch, useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useFeatures, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { capitalize } from 'soapbox/utils/strings';
|
||||
|
||||
const LandingPage = () => {
|
||||
|
@ -15,7 +15,7 @@ const LandingPage = () => {
|
|||
const soapboxConfig = useSoapboxConfig();
|
||||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const instance = useInstance();
|
||||
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
|
||||
|
||||
/** Registrations are closed */
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Link } from 'react-router-dom';
|
|||
import { moveAccount } from 'soapbox/actions/security';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Column, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.migration', defaultMessage: 'Account migration' },
|
||||
|
@ -21,8 +21,9 @@ const messages = defineMessages({
|
|||
const Migration = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
|
||||
const cooldownPeriod = useAppSelector((state) => state.instance.pleroma.getIn(['metadata', 'migration_cooldown_period'])) as number | undefined;
|
||||
const cooldownPeriod = instance.pleroma.getIn(['metadata', 'migration_cooldown_period']) as number | undefined;
|
||||
|
||||
const [targetAccount, setTargetAccount] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
|
|
@ -12,7 +12,7 @@ import Icon from 'soapbox/components/icon';
|
|||
import { HStack, Text, Emoji } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import StatusContainer from 'soapbox/containers/status-container';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
import { makeGetNotification } from 'soapbox/selectors';
|
||||
import { NotificationType, validType } from 'soapbox/utils/notification';
|
||||
|
||||
|
@ -157,7 +157,7 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
|
||||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const instance = useInstance();
|
||||
|
||||
const type = notification.type;
|
||||
const { account, status } = notification;
|
||||
|
|
|
@ -3,13 +3,13 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
import Account from 'soapbox/components/account';
|
||||
import { Button, Card, CardBody, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
|
||||
import { useInstance, useOwnAccount } from 'soapbox/hooks';
|
||||
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
|
||||
const FediverseStep = ({ onNext }: { onNext: () => void }) => {
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
const account = useOwnAccount() as AccountEntity;
|
||||
const instance = useInstance();
|
||||
|
||||
return (
|
||||
<Card variant='rounded' size='xl'>
|
||||
|
@ -22,7 +22,7 @@ const FediverseStep = ({ onNext }: { onNext: () => void }) => {
|
|||
id='onboarding.fediverse.title'
|
||||
defaultMessage='{siteTitle} is just one part of the Fediverse'
|
||||
values={{
|
||||
siteTitle,
|
||||
siteTitle: instance.title,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
|
@ -35,7 +35,7 @@ const FediverseStep = ({ onNext }: { onNext: () => void }) => {
|
|||
id='onboarding.fediverse.message'
|
||||
defaultMessage='The Fediverse is a social network made up of thousands of diverse and independently-run social media sites (aka "servers"). You can follow users — and like, repost, and reply to posts — from most other Fediverse servers, because they can communicate with {siteTitle}.'
|
||||
values={{
|
||||
siteTitle,
|
||||
siteTitle: instance.title,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
|
|
|
@ -8,7 +8,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 } from 'soapbox/hooks';
|
||||
import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount, useInstance } from 'soapbox/hooks';
|
||||
|
||||
import Sonar from './sonar';
|
||||
|
||||
|
@ -34,7 +34,7 @@ const Header = () => {
|
|||
const { links } = soapboxConfig;
|
||||
|
||||
const features = useFeatures();
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const instance = useInstance();
|
||||
const isOpen = features.accountCreation && instance.registrations;
|
||||
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { expandPublicTimeline } from 'soapbox/actions/timelines';
|
|||
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
||||
import SubNavigation from 'soapbox/components/sub-navigation';
|
||||
import { Accordion, Column } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useInstance, useSettings } from 'soapbox/hooks';
|
||||
|
||||
import PinnedHostsPicker from '../remote-timeline/components/pinned-hosts-picker';
|
||||
import Timeline from '../ui/components/timeline';
|
||||
|
@ -22,12 +22,12 @@ const CommunityTimeline = () => {
|
|||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const instance = useInstance();
|
||||
const settings = useSettings();
|
||||
const onlyMedia = settings.getIn(['public', 'other', 'onlyMedia']);
|
||||
|
||||
const timelineId = 'public';
|
||||
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
const explanationBoxExpanded = settings.get('explanationBox');
|
||||
const showExplanationBox = settings.get('showExplanationBox');
|
||||
|
||||
|
@ -79,13 +79,13 @@ const CommunityTimeline = () => {
|
|||
id='fediverse_tab.explanation_box.explanation'
|
||||
defaultMessage='{site_title} is part of the Fediverse, a social network made up of thousands of independent social media sites (aka "servers"). The posts you see here are from 3rd-party servers. You have the freedom to engage with them, or to block any server you don't like. Pay attention to the full username after the second @ symbol to know which server a post is from. To see only {site_title} posts, visit {local}.'
|
||||
values={{
|
||||
site_title: siteTitle,
|
||||
site_title: instance.title,
|
||||
local: (
|
||||
<Link to='/timeline/local'>
|
||||
<FormattedMessage
|
||||
id='empty_column.home.local_tab'
|
||||
defaultMessage='the {site_title} tab'
|
||||
values={{ site_title: siteTitle }}
|
||||
values={{ site_title: instance.title }}
|
||||
/>
|
||||
</Link>
|
||||
),
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useParams } from 'react-router-dom';
|
|||
|
||||
import { Stack, CardTitle, Text } from 'soapbox/components/ui';
|
||||
import RegistrationForm from 'soapbox/features/auth-login/components/registration-form';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
|
||||
interface RegisterInviteParams {
|
||||
token: string,
|
||||
|
@ -12,14 +12,14 @@ interface RegisterInviteParams {
|
|||
|
||||
/** Page to register with an invitation. */
|
||||
const RegisterInvite: React.FC = () => {
|
||||
const instance = useInstance();
|
||||
const { token } = useParams<RegisterInviteParams>();
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id='register_invite.title'
|
||||
defaultMessage="You've been invited to join {siteTitle}!"
|
||||
values={{ siteTitle }}
|
||||
values={{ siteTitle: instance.title }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { Column, Divider, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
|
||||
import LinkFooter from '../ui/components/link-footer';
|
||||
import PromoPanel from '../ui/components/promo-panel';
|
||||
|
@ -13,7 +13,7 @@ const messages = defineMessages({
|
|||
|
||||
const ServerInfo = () => {
|
||||
const intl = useIntl();
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const instance = useInstance();
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
|
|
|
@ -6,8 +6,7 @@ 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, useOwnAccount } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
|
||||
import Preferences from '../preferences';
|
||||
|
||||
|
@ -38,7 +37,7 @@ const Settings = () => {
|
|||
const intl = useIntl();
|
||||
|
||||
const mfa = useAppSelector((state) => state.security.get('mfa'));
|
||||
const features = useAppSelector((state) => getFeatures(state.instance));
|
||||
const features = useFeatures();
|
||||
const account = useOwnAccount();
|
||||
|
||||
const navigateToChangeEmail = () => history.push('/settings/email');
|
||||
|
|
|
@ -127,7 +127,7 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<HStack justifyContent='between' alignItems='center' className='py-2' wrap>
|
||||
<HStack justifyContent='between' alignItems='center' className='py-3' wrap>
|
||||
<StatusInteractionBar status={actualStatus} />
|
||||
|
||||
<HStack space={1} alignItems='center'>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import classNames from 'clsx';
|
||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React from 'react';
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { HStack, IconButton, Text, Emoji } from 'soapbox/components/ui';
|
||||
import { HStack, Text, Emoji } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useSoapboxConfig, useFeatures } from 'soapbox/hooks';
|
||||
import { reduceEmoji } from 'soapbox/utils/emoji-reacts';
|
||||
|
||||
|
@ -42,11 +42,10 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
}));
|
||||
};
|
||||
|
||||
const onOpenReactionsModal = (username: string, statusId: string, reaction: string): void => {
|
||||
const onOpenReactionsModal = (username: string, statusId: string): void => {
|
||||
dispatch(openModal('REACTIONS', {
|
||||
username,
|
||||
statusId,
|
||||
reaction,
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -56,7 +55,7 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
status.favourites_count,
|
||||
status.favourited,
|
||||
allowedEmoji,
|
||||
).reverse();
|
||||
);
|
||||
};
|
||||
|
||||
const handleOpenReblogsModal: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
|
@ -69,22 +68,17 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
const getReposts = () => {
|
||||
if (status.reblogs_count) {
|
||||
return (
|
||||
<HStack space={0.5} alignItems='center'>
|
||||
<IconButton
|
||||
className='text-success-600 cursor-pointer'
|
||||
src={require('@tabler/icons/repeat.svg')}
|
||||
role='presentation'
|
||||
onClick={handleOpenReblogsModal}
|
||||
<InteractionCounter count={status.reblogs_count} onClick={handleOpenReblogsModal}>
|
||||
<FormattedMessage
|
||||
id='status.interactions.reblogs'
|
||||
defaultMessage='{count, plural, one {Repost} other {Reposts}}'
|
||||
values={{ count: status.reblogs_count }}
|
||||
/>
|
||||
|
||||
<Text theme='muted' size='sm'>
|
||||
<FormattedNumber value={status.reblogs_count} />
|
||||
</Text>
|
||||
</HStack>
|
||||
</InteractionCounter>
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleOpenFavouritesModal: React.EventHandler<React.MouseEvent<HTMLButtonElement>> = (e) => {
|
||||
|
@ -97,31 +91,25 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
const getFavourites = () => {
|
||||
if (status.favourites_count) {
|
||||
return (
|
||||
<HStack space={0.5} alignItems='center'>
|
||||
<IconButton
|
||||
className={classNames({
|
||||
'text-accent-300': true,
|
||||
'cursor-default': !features.exposableReactions,
|
||||
})}
|
||||
src={require('@tabler/icons/heart.svg')}
|
||||
iconClassName='fill-accent-300'
|
||||
role='presentation'
|
||||
onClick={features.exposableReactions ? handleOpenFavouritesModal : undefined}
|
||||
<InteractionCounter count={status.favourites_count} onClick={features.exposableReactions ? handleOpenFavouritesModal : undefined}>
|
||||
<FormattedMessage
|
||||
id='status.interactions.favourites'
|
||||
defaultMessage='{count, plural, one {Like} other {Likes}}'
|
||||
values={{ count: status.favourites_count }}
|
||||
/>
|
||||
|
||||
<Text theme='muted' size='sm'>
|
||||
<FormattedNumber value={status.favourites_count} />
|
||||
</Text>
|
||||
</HStack>
|
||||
</InteractionCounter>
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleOpenReactionsModal = (reaction: ImmutableMap<string, any>) => () => {
|
||||
if (!me) onOpenUnauthorizedModal();
|
||||
else onOpenReactionsModal(account.acct, status.id, String(reaction.get('name')));
|
||||
const handleOpenReactionsModal = () => {
|
||||
if (!me) {
|
||||
return onOpenUnauthorizedModal();
|
||||
}
|
||||
|
||||
onOpenReactionsModal(account.acct, status.id);
|
||||
};
|
||||
|
||||
const getEmojiReacts = () => {
|
||||
|
@ -130,43 +118,67 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
|||
acc + cur.get('count')
|
||||
), 0);
|
||||
|
||||
if (count > 0) {
|
||||
if (count) {
|
||||
return (
|
||||
<HStack space={0.5} className='emoji-reacts-container' alignItems='center'>
|
||||
<div className='emoji-reacts'>
|
||||
{emojiReacts.map((e, i) => {
|
||||
<InteractionCounter count={count} onClick={features.exposableReactions ? handleOpenReactionsModal : undefined}>
|
||||
<HStack space={0.5} alignItems='center'>
|
||||
{emojiReacts.take(3).map((e, i) => {
|
||||
return (
|
||||
<HStack space={0.5} className='emoji-react p-1' alignItems='center' key={i}>
|
||||
<Emoji
|
||||
className={classNames('emoji-react__emoji w-5 h-5 flex-none', { 'cursor-pointer': features.exposableReactions })}
|
||||
emoji={e.get('name')}
|
||||
onClick={features.exposableReactions ? handleOpenReactionsModal(e) : undefined}
|
||||
/>
|
||||
<Text theme='muted' size='sm' className='emoji-react__count'>
|
||||
<FormattedNumber value={e.get('count')} />
|
||||
</Text>
|
||||
</HStack>
|
||||
<Emoji
|
||||
key={i}
|
||||
className='w-4.5 h-4.5 flex-none'
|
||||
emoji={e.get('name')}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Text theme='muted' size='sm' className='emoji-reacts__count'>
|
||||
<FormattedNumber value={count} />
|
||||
</Text>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</InteractionCounter>
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack space={3}>
|
||||
{getReposts()}
|
||||
|
||||
{features.emojiReacts ? getEmojiReacts() : getFavourites()}
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
interface IInteractionCounter {
|
||||
count: number,
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>,
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
const InteractionCounter: React.FC<IInteractionCounter> = ({ count, onClick, children }) => {
|
||||
const features = useFeatures();
|
||||
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
onClick={onClick}
|
||||
className={
|
||||
classNames({
|
||||
'text-gray-600 dark:text-gray-700': true,
|
||||
'hover:underline': features.exposableReactions,
|
||||
'cursor-default': !features.exposableReactions,
|
||||
})
|
||||
}
|
||||
>
|
||||
<HStack space={1} alignItems='center'>
|
||||
<Text theme='primary' weight='bold'>
|
||||
<FormattedNumber value={count} />
|
||||
</Text>
|
||||
|
||||
<Text tag='div' theme='muted'>
|
||||
{children}
|
||||
</Text>
|
||||
</HStack>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusInteractionBar;
|
||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Card, CardTitle, Text, Stack, Button } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
/** Prompts logged-out users to log in when viewing a thread. */
|
||||
const ThreadLoginCta: React.FC = () => {
|
||||
const instance = useInstance();
|
||||
const { displayCta } = useSoapboxConfig();
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
|
||||
if (!displayCta) return null;
|
||||
|
||||
|
@ -19,7 +19,7 @@ const ThreadLoginCta: React.FC = () => {
|
|||
<FormattedMessage
|
||||
id='thread_login.message'
|
||||
defaultMessage='Join {siteTitle} to get the full story and details.'
|
||||
values={{ siteTitle }}
|
||||
values={{ siteTitle: instance.title }}
|
||||
/>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
|
|
@ -2,11 +2,11 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Banner, Button, HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
const CtaBanner = () => {
|
||||
const instance = useInstance();
|
||||
const { displayCta, singleUserMode } = useSoapboxConfig();
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
const me = useAppSelector((state) => state.me);
|
||||
|
||||
if (me || !displayCta || singleUserMode) return null;
|
||||
|
@ -17,7 +17,7 @@ const CtaBanner = () => {
|
|||
<HStack alignItems='center' justifyContent='between'>
|
||||
<Stack>
|
||||
<Text theme='white' size='xl' weight='bold'>
|
||||
<FormattedMessage id='signup_panel.title' defaultMessage='New to {site_title}?' values={{ site_title: siteTitle }} />
|
||||
<FormattedMessage id='signup_panel.title' defaultMessage='New to {site_title}?' values={{ site_title: instance.title }} />
|
||||
</Text>
|
||||
|
||||
<Text theme='white' weight='medium' className='opacity-90'>
|
||||
|
|
|
@ -2,8 +2,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Modal } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { useFeatures } from 'soapbox/hooks';
|
||||
|
||||
interface IHotkeysModal {
|
||||
onClose: () => void,
|
||||
|
@ -22,7 +21,7 @@ const TableCell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|||
);
|
||||
|
||||
const HotkeysModal: React.FC<IHotkeysModal> = ({ onClose }) => {
|
||||
const features = useAppSelector((state) => getFeatures(state.instance));
|
||||
const features = useFeatures();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
|
@ -4,7 +4,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { Text, Button, Icon, Modal } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppSelector, useFeatures, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
download: { id: 'landing_page_modal.download', defaultMessage: 'Download' },
|
||||
|
@ -25,7 +25,7 @@ const LandingPageModal: React.FC<ILandingPageModal> = ({ onClose }) => {
|
|||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
const { links } = soapboxConfig;
|
||||
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
|
||||
const isOpen = features.accountCreation && instance.registrations;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useHistory } from 'react-router-dom';
|
|||
import { remoteInteraction } from 'soapbox/actions/interactions';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Modal, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppSelector, useAppDispatch, useFeatures, useSoapboxConfig, useInstance } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -29,9 +29,9 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
|||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
|
||||
const { singleUserMode } = useSoapboxConfig();
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
const username = useAppSelector(state => state.accounts.get(accountId)?.display_name);
|
||||
const features = useFeatures();
|
||||
|
||||
|
@ -121,7 +121,7 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
|||
</div>
|
||||
{!singleUserMode && (
|
||||
<Text size='lg' weight='medium'>
|
||||
<FormattedMessage id='unauthorized_modal.title' defaultMessage='Sign up for {site_title}' values={{ site_title: siteTitle }} />
|
||||
<FormattedMessage id='unauthorized_modal.title' defaultMessage='Sign up for {site_title}' values={{ site_title: instance.title }} />
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
@ -135,7 +135,7 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title={<FormattedMessage id='unauthorized_modal.title' defaultMessage='Sign up for {site_title}' values={{ site_title: siteTitle }} />}
|
||||
title={<FormattedMessage id='unauthorized_modal.title' defaultMessage='Sign up for {site_title}' values={{ site_title: instance.title }} />}
|
||||
onClose={onClickClose}
|
||||
confirmationAction={onLogin}
|
||||
confirmationText={<FormattedMessage id='account.login' defaultMessage='Log in' />}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { closeModal } from 'soapbox/actions/modals';
|
|||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { reConfirmPhoneVerification, reRequestPhoneVerification } from 'soapbox/actions/verification';
|
||||
import { FormGroup, PhoneInput, Modal, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
import { getAccessToken } from 'soapbox/utils/auth';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -56,8 +56,8 @@ enum Statuses {
|
|||
const VerifySmsModal: React.FC<IVerifySmsModal> = ({ onClose }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const instance = useInstance();
|
||||
const accessToken = useAppSelector((state) => getAccessToken(state));
|
||||
const title = useAppSelector((state) => state.instance.title);
|
||||
const isLoading = useAppSelector((state) => state.verification.isLoading);
|
||||
|
||||
const [status, setStatus] = useState<Statuses>(Statuses.IDLE);
|
||||
|
@ -143,7 +143,7 @@ const VerifySmsModal: React.FC<IVerifySmsModal> = ({ onClose }) => {
|
|||
id='sms_verification.modal.verify_help_text'
|
||||
defaultMessage='Verify your phone number to start using {instance}.'
|
||||
values={{
|
||||
instance: title,
|
||||
instance: instance.title,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
|
|
|
@ -2,11 +2,11 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Button, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
const SignUpPanel = () => {
|
||||
const instance = useInstance();
|
||||
const { singleUserMode } = useSoapboxConfig();
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
const me = useAppSelector((state) => state.me);
|
||||
|
||||
if (me || singleUserMode) return null;
|
||||
|
@ -15,7 +15,7 @@ const SignUpPanel = () => {
|
|||
<Stack space={2}>
|
||||
<Stack>
|
||||
<Text size='lg' weight='bold'>
|
||||
<FormattedMessage id='signup_panel.title' defaultMessage='New to {site_title}?' values={{ site_title: siteTitle }} />
|
||||
<FormattedMessage id='signup_panel.title' defaultMessage='New to {site_title}?' values={{ site_title: instance.title }} />
|
||||
</Text>
|
||||
|
||||
<Text theme='muted' size='sm'>
|
||||
|
|
|
@ -2,20 +2,20 @@ import React from 'react';
|
|||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { Widget, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useInstance, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
const PromoPanel: React.FC = () => {
|
||||
const instance = useInstance();
|
||||
const { promoPanel } = useSoapboxConfig();
|
||||
const settings = useSettings();
|
||||
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
const promoItems = promoPanel.get('items');
|
||||
const locale = settings.get('locale');
|
||||
|
||||
if (!promoItems || promoItems.isEmpty()) return null;
|
||||
|
||||
return (
|
||||
<Widget title={siteTitle}>
|
||||
<Widget title={instance.title}>
|
||||
<Stack space={2}>
|
||||
{promoItems.map((item, i) => (
|
||||
<Text key={i}>
|
||||
|
|
|
@ -24,7 +24,8 @@ import Icon from 'soapbox/components/icon';
|
|||
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
||||
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
||||
import { Layout } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures } from 'soapbox/hooks';
|
||||
import { StatProvider } from 'soapbox/contexts/stat-context';
|
||||
import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
import AdminPage from 'soapbox/pages/admin-page';
|
||||
import ChatsPage from 'soapbox/pages/chats-page';
|
||||
import DefaultPage from 'soapbox/pages/default-page';
|
||||
|
@ -36,8 +37,6 @@ import { usePendingPolicy } from 'soapbox/queries/policies';
|
|||
import { getAccessToken, getVapidKey } from 'soapbox/utils/auth';
|
||||
import { isStandalone } from 'soapbox/utils/state';
|
||||
|
||||
import { StatProvider } from '../../contexts/stat-context';
|
||||
|
||||
import BackgroundShapes from './components/background-shapes';
|
||||
import { supportedPolicyIds } from './components/modals/policy-modal';
|
||||
import Navbar from './components/navbar';
|
||||
|
@ -316,6 +315,7 @@ const UI: React.FC = ({ children }) => {
|
|||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: pendingPolicy } = usePendingPolicy();
|
||||
const instance = useInstance();
|
||||
|
||||
const [draggingOver, setDraggingOver] = useState<boolean>(false);
|
||||
const [mobile, setMobile] = useState<boolean>(isMobile(window.innerWidth));
|
||||
|
@ -332,7 +332,7 @@ const UI: React.FC = ({ children }) => {
|
|||
|
||||
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.openId !== null);
|
||||
const accessToken = useAppSelector(state => getAccessToken(state));
|
||||
const streamingUrl = useAppSelector(state => state.instance.urls.get('streaming_api'));
|
||||
const streamingUrl = instance.urls.get('streaming_api');
|
||||
const standalone = useAppSelector(isStandalone);
|
||||
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { startOnboarding } from 'soapbox/actions/onboarding';
|
|||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { createAccount, removeStoredVerification } from 'soapbox/actions/verification';
|
||||
import { Button, Form, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { getRedirectUrl } from 'soapbox/utils/redirect';
|
||||
|
||||
import PasswordIndicator from './components/password-indicator';
|
||||
|
@ -32,11 +32,11 @@ const initialState = {
|
|||
const Registration = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const instance = useInstance();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const { links } = soapboxConfig;
|
||||
|
||||
const isLoading = useAppSelector((state) => state.verification.isLoading as boolean);
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
|
||||
const [state, setState] = React.useState(initialState);
|
||||
const [shouldRedirect, setShouldRedirect] = React.useState<boolean>(false);
|
||||
|
@ -56,7 +56,7 @@ const Registration = () => {
|
|||
dispatch(startOnboarding());
|
||||
dispatch(
|
||||
snackbar.success(
|
||||
intl.formatMessage(messages.success, { siteTitle }),
|
||||
intl.formatMessage(messages.success, { siteTitle: instance.title }),
|
||||
),
|
||||
);
|
||||
})
|
||||
|
|
|
@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { verifyAge } from 'soapbox/actions/verification';
|
||||
import { Button, Datepicker, Form, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
fail: {
|
||||
|
@ -24,10 +24,10 @@ function meetsAgeMinimum(birthday: Date, ageMinimum: number) {
|
|||
const AgeVerification = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
|
||||
const isLoading = useAppSelector((state) => state.verification.isLoading) as boolean;
|
||||
const ageMinimum = useAppSelector((state) => state.verification.ageMinimum) as any;
|
||||
const siteTitle = useAppSelector((state) => state.instance.title);
|
||||
|
||||
const [date, setDate] = React.useState('');
|
||||
const isValid = typeof date === 'object';
|
||||
|
@ -65,7 +65,7 @@ const AgeVerification = () => {
|
|||
id='age_verification.body'
|
||||
defaultMessage='{siteTitle} requires users to be at least {ageMinimum} years old to access its platform. Anyone under the age of {ageMinimum} years old cannot access this platform.'
|
||||
values={{
|
||||
siteTitle,
|
||||
siteTitle: instance.title,
|
||||
ageMinimum,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -8,11 +8,11 @@ import { openModal } from 'soapbox/actions/modals';
|
|||
import LandingGradient from 'soapbox/components/landing-gradient';
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { Button, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
|
||||
import { useInstance, useOwnAccount } from 'soapbox/hooks';
|
||||
|
||||
const WaitlistPage = (/* { account } */) => {
|
||||
const WaitlistPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const title = useAppSelector((state) => state.instance.title);
|
||||
const instance = useInstance();
|
||||
|
||||
const me = useOwnAccount();
|
||||
const isSmsVerified = me?.source.get('sms_verified');
|
||||
|
@ -59,7 +59,7 @@ const WaitlistPage = (/* { account } */) => {
|
|||
<FormattedMessage
|
||||
id='waitlist.body'
|
||||
defaultMessage='Welcome back to {title}! You were previously placed on our waitlist. Please verify your phone number to receive immediate access to your account!'
|
||||
values={{ title }}
|
||||
values={{ title: instance.title }}
|
||||
/>
|
||||
</Text>
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ export { useCompose } from './useCompose';
|
|||
export { useDebounce } from './useDebounce';
|
||||
export { useDimensions } from './useDimensions';
|
||||
export { useFeatures } from './useFeatures';
|
||||
export { useInstance } from './useInstance';
|
||||
export { useLocale } from './useLocale';
|
||||
export { useOnScreen } from './useOnScreen';
|
||||
export { useOwnAccount } from './useOwnAccount';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import { getFeatures, Features } from 'soapbox/utils/features';
|
||||
|
||||
import type { Features } from 'soapbox/utils/features';
|
||||
import { useInstance } from './useInstance';
|
||||
|
||||
/** Get features for the current instance */
|
||||
/** Get features for the current instance. */
|
||||
export const useFeatures = (): Features => {
|
||||
return useAppSelector((state) => getFeatures(state.instance));
|
||||
const instance = useInstance();
|
||||
return getFeatures(instance);
|
||||
};
|
||||
|
|
6
app/soapbox/hooks/useInstance.ts
Normal file
6
app/soapbox/hooks/useInstance.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
/** Get the Instance for the current backend. */
|
||||
export const useInstance = () => {
|
||||
return useAppSelector((state) => state.instance);
|
||||
};
|
|
@ -1,13 +1,21 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
||||
import type { Account } from 'soapbox/types/entities';
|
||||
|
||||
// FIXME: There is no reason this selector shouldn't be global accross the whole app
|
||||
// FIXME: getAccount() has the wrong type??
|
||||
const getAccount: (state: any, accountId: any) => any = makeGetAccount();
|
||||
|
||||
/** Get the logged-in account from the store, if any */
|
||||
/** Get the logged-in account from the store, if any. */
|
||||
export const useOwnAccount = (): Account | null => {
|
||||
return useAppSelector((state) => getAccount(state, state.me));
|
||||
const getAccount = useCallback(makeGetAccount(), []);
|
||||
|
||||
return useAppSelector((state) => {
|
||||
const { me } = state;
|
||||
|
||||
if (typeof me === 'string') {
|
||||
return getAccount(state, me);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -996,6 +996,8 @@
|
|||
"status.in_review_summary.summary": "This post has been sent to Moderation for review and is only visible to you.",
|
||||
"status.in_review_summary.contact": "If you believe this is in error please {link}.",
|
||||
"status.in_review_summary.link": "Contact Support",
|
||||
"status.interactions.reblogs": "{count, plural, one {Repost} other {Reposts}}",
|
||||
"status.interactions.favourites": "{count, plural, one {Like} other {Likes}}",
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Mention @{name}",
|
||||
|
|
|
@ -43,6 +43,9 @@ module.exports = {
|
|||
'mono',
|
||||
],
|
||||
},
|
||||
spacing: {
|
||||
'4.5': '1.125rem',
|
||||
},
|
||||
colors: parseColorMatrix({
|
||||
// Define color matrix (of available colors)
|
||||
// Colors are configured at runtime with CSS variables in soapbox.json
|
||||
|
|
Loading…
Reference in a new issue