From 3a0e7537895c1edf991e03ad997fb0e311e38391 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 17 Nov 2022 15:03:30 -0500 Subject: [PATCH 1/7] Update design to likes / reposts in interaction bar --- .../status/components/detailed-status.tsx | 2 +- .../components/status-interaction-bar.tsx | 66 +++++++++++-------- app/soapbox/locales/en.json | 2 + 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/app/soapbox/features/status/components/detailed-status.tsx b/app/soapbox/features/status/components/detailed-status.tsx index a1fdc5434..8405f5bed 100644 --- a/app/soapbox/features/status/components/detailed-status.tsx +++ b/app/soapbox/features/status/components/detailed-status.tsx @@ -127,7 +127,7 @@ const DetailedStatus: React.FC = ({ - + diff --git a/app/soapbox/features/status/components/status-interaction-bar.tsx b/app/soapbox/features/status/components/status-interaction-bar.tsx index a0abcf98e..a92ed2fc6 100644 --- a/app/soapbox/features/status/components/status-interaction-bar.tsx +++ b/app/soapbox/features/status/components/status-interaction-bar.tsx @@ -1,7 +1,7 @@ import classNames from 'clsx'; import { Map as ImmutableMap, 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'; @@ -69,18 +69,25 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. const getReposts = () => { if (status.reblogs_count) { return ( - - + ); } @@ -97,22 +104,27 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. const getFavourites = () => { if (status.favourites_count) { return ( - - + ); } diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index b7c280137..fcdda9c46 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -994,6 +994,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}", From 1dfd5244f0b63dddf953d2d3d59cd4e20913bab0 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 17 Nov 2022 15:37:58 -0500 Subject: [PATCH 2/7] Update design for emoji reacts --- .../components/status-interaction-bar.tsx | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/app/soapbox/features/status/components/status-interaction-bar.tsx b/app/soapbox/features/status/components/status-interaction-bar.tsx index a92ed2fc6..34de4f692 100644 --- a/app/soapbox/features/status/components/status-interaction-bar.tsx +++ b/app/soapbox/features/status/components/status-interaction-bar.tsx @@ -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 { 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,19 +42,18 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. })); }; - const onOpenReactionsModal = (username: string, statusId: string, reaction: string): void => { + const onOpenReactionsModal = (username: string, statusId: string): void => { dispatch(openModal('REACTIONS', { username, statusId, - reaction, })); }; const getNormalizedReacts = () => { return reduceEmoji( ImmutableList(status.pleroma.get('emoji_reactions') as any), - status.favourites_count, - status.favourited, + 0, + false, allowedEmoji, ).reverse(); }; @@ -107,15 +106,19 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. + ); }; return ( {getReposts()} - - {features.emojiReacts ? getEmojiReacts() : getFavourites()} + {getFavourites()} + {features.emojiReacts ? getEmojiReacts() : null} ); }; From b9a3f7ec13f987bd5c2a20f6d5143c4ec47a9133 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 20 Nov 2022 16:05:53 -0600 Subject: [PATCH 3/7] StatusInteractionBar: break into InteractionCounter component --- app/soapbox/components/ui/text/text.tsx | 2 +- .../components/status-interaction-bar.tsx | 72 ++++++++++--------- tailwind.config.js | 3 + 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/app/soapbox/components/ui/text/text.tsx b/app/soapbox/components/ui/text/text.tsx index 7669f3d2a..0f8d4e67b 100644 --- a/app/soapbox/components/ui/text/text.tsx +++ b/app/soapbox/components/ui/text/text.tsx @@ -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, 'dangerouslySetInnerHTML'> { diff --git a/app/soapbox/features/status/components/status-interaction-bar.tsx b/app/soapbox/features/status/components/status-interaction-bar.tsx index 34de4f692..e6f1358fe 100644 --- a/app/soapbox/features/status/components/status-interaction-bar.tsx +++ b/app/soapbox/features/status/components/status-interaction-bar.tsx @@ -73,19 +73,13 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. onClick={handleOpenReblogsModal} className='text-gray-600 dark:text-gray-700 hover:underline' > - - - - - - - - - + + + ); } @@ -114,19 +108,13 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. }) } > - - - - - - - - - + + + ); } @@ -160,23 +148,19 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. }) } > - - - - - + - {emojiReacts.map((e, i) => { + {emojiReacts.take(3).map((e, i) => { return ( ); })} - + ); }; @@ -190,4 +174,24 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. ); }; +interface IInteractionCounter { + count: number, + children: React.ReactNode, +} + +/** InteractionCounter component. */ +const InteractionCounter: React.FC = ({ count, children }) => { + return ( + + + + + + + {children} + + + ); +}; + export default StatusInteractionBar; diff --git a/tailwind.config.js b/tailwind.config.js index 7d562327b..1e2625c6f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -42,6 +42,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 From de3678c2721cbb1c0e279a6e447dfc1748678d8e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 20 Nov 2022 16:14:50 -0600 Subject: [PATCH 4/7] InteractionCounter: refactor with + + + ); } - return ''; + return null; }; const handleOpenFavouritesModal: React.EventHandler> = (e) => { @@ -97,29 +91,17 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. const getFavourites = () => { if (status.favourites_count) { return ( - + + + ); } - return ''; + return null; }; const handleOpenReactionsModal = () => { @@ -136,19 +118,9 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. acc + cur.get('count') ), 0); - return ( - - ); + ); + } + + return null; }; return ( {getReposts()} {getFavourites()} - {features.emojiReacts ? getEmojiReacts() : null} + {features.emojiReacts && getEmojiReacts()} ); }; interface IInteractionCounter { count: number, + onClick?: React.MouseEventHandler, children: React.ReactNode, } -/** InteractionCounter component. */ -const InteractionCounter: React.FC = ({ count, children }) => { - return ( - - - - +const InteractionCounter: React.FC = ({ count, onClick, children }) => { + const features = useFeatures(); - - {children} - - + return ( + ); }; From d3b67e5167cef94a93d6699fc57f3b1a48047893 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 26 Nov 2022 10:38:16 -0600 Subject: [PATCH 5/7] Add useInstance() hook --- app/soapbox/components/birthday-input.tsx | 5 +++-- app/soapbox/components/gdpr-banner.tsx | 8 +++---- app/soapbox/components/helmet.tsx | 8 +++---- app/soapbox/components/sidebar-navigation.tsx | 10 +++------ app/soapbox/components/thumb-navigation.tsx | 5 ++--- app/soapbox/containers/soapbox.tsx | 3 ++- .../features/account-gallery/index.tsx | 5 ++--- .../components/registration-mode-picker.tsx | 5 +++-- app/soapbox/features/admin/tabs/dashboard.tsx | 4 ++-- app/soapbox/features/ads/components/ad.tsx | 4 ++-- .../features/aliases/components/account.tsx | 11 ++++------ app/soapbox/features/aliases/index.tsx | 21 ++++++++----------- app/soapbox/features/auth-layout/index.tsx | 13 ++++++------ .../auth-login/components/consumers-list.tsx | 5 +++-- .../components/registration-form.tsx | 4 ++-- .../compose/components/compose-form.tsx | 5 +++-- .../compose/components/polls/poll-form.tsx | 10 +++++---- .../compose/components/reply-mentions.tsx | 12 ++++------- .../compose/components/upload-button.tsx | 5 +++-- .../features/compose/components/upload.tsx | 6 +++--- .../components/crypto-donate-panel.tsx | 6 +++--- app/soapbox/features/crypto-donate/index.tsx | 9 ++++---- app/soapbox/features/directory/index.tsx | 9 ++++---- app/soapbox/features/edit-profile/index.tsx | 5 +++-- .../components/instance-restrictions.tsx | 4 ++-- .../federation-restrictions/index.tsx | 14 ++++++------- app/soapbox/features/home-timeline/index.tsx | 8 +++---- app/soapbox/features/landing-page/index.tsx | 4 ++-- app/soapbox/features/migration/index.tsx | 5 +++-- .../notifications/components/notification.tsx | 4 ++-- .../onboarding/steps/fediverse-step.tsx | 8 +++---- .../public-layout/components/header.tsx | 4 ++-- .../features/public-timeline/index.tsx | 8 +++---- .../features/register-invite/index.tsx | 6 +++--- app/soapbox/features/server-info/index.tsx | 4 ++-- app/soapbox/features/settings/index.tsx | 5 ++--- .../status/components/thread-login-cta.tsx | 6 +++--- .../features/ui/components/cta-banner.tsx | 6 +++--- .../ui/components/modals/hotkeys-modal.tsx | 5 ++--- .../components/modals/landing-page-modal.tsx | 4 ++-- .../components/modals/unauthorized-modal.tsx | 8 +++---- .../ui/components/modals/verify-sms-modal.tsx | 6 +++--- .../ui/components/panels/sign-up-panel.tsx | 6 +++--- .../features/ui/components/promo-panel.tsx | 6 +++--- app/soapbox/features/ui/index.tsx | 20 +++--------------- .../features/verification/registration.tsx | 6 +++--- .../verification/steps/age-verification.tsx | 6 +++--- .../features/verification/waitlist-page.tsx | 8 +++---- app/soapbox/hooks/index.ts | 1 + app/soapbox/hooks/useFeatures.ts | 10 ++++----- app/soapbox/hooks/useInstance.ts | 6 ++++++ app/soapbox/hooks/useOwnAccount.ts | 20 ++++++++++++------ 52 files changed, 184 insertions(+), 192 deletions(-) create mode 100644 app/soapbox/hooks/useInstance.ts diff --git a/app/soapbox/components/birthday-input.tsx b/app/soapbox/components/birthday-input.tsx index 21a10a1e5..9da717e0d 100644 --- a/app/soapbox/components/birthday-input.tsx +++ b/app/soapbox/components/birthday-input.tsx @@ -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 = ({ 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; diff --git a/app/soapbox/components/gdpr-banner.tsx b/app/soapbox/components/gdpr-banner.tsx index 94ea0cf59..0c9ea85c8 100644 --- a/app/soapbox/components/gdpr-banner.tsx +++ b/app/soapbox/components/gdpr-banner.tsx @@ -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(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 = () => {
- + diff --git a/app/soapbox/components/helmet.tsx b/app/soapbox/components/helmet.tsx index d3a5bc021..23a73e744 100644 --- a/app/soapbox/components/helmet.tsx +++ b/app/soapbox/components/helmet.tsx @@ -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 ( {children} diff --git a/app/soapbox/components/sidebar-navigation.tsx b/app/soapbox/components/sidebar-navigation.tsx index 28d64ed8f..4e8e73e5d 100644 --- a/app/soapbox/components/sidebar-navigation.tsx +++ b/app/soapbox/components/sidebar-navigation.tsx @@ -1,11 +1,9 @@ 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 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'; @@ -22,16 +20,14 @@ const messages = defineMessages({ const SidebarNavigation = () => { const intl = useIntl(); - 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 chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0)); 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 = []; diff --git a/app/soapbox/components/thumb-navigation.tsx b/app/soapbox/components/thumb-navigation.tsx index 45a980f86..16007104a 100644 --- a/app/soapbox/components/thumb-navigation.tsx +++ b/app/soapbox/components/thumb-navigation.tsx @@ -2,15 +2,14 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import ThumbNavigationLink from 'soapbox/components/thumb-navigation-link'; -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(); const notificationCount = useAppSelector((state) => state.notifications.unread); const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0)); 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 => { diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index ce02d068d..74f53967d 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -37,6 +37,7 @@ import { useSettings, useTheme, useLocale, + useInstance, } from 'soapbox/hooks'; import MESSAGES from 'soapbox/locales/messages'; import { queryClient } from 'soapbox/queries/client'; @@ -85,7 +86,7 @@ const loadInitial = () => { 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(); diff --git a/app/soapbox/features/account-gallery/index.tsx b/app/soapbox/features/account-gallery/index.tsx index da6fb226a..9fda9751f 100644 --- a/app/soapbox/features/account-gallery/index.tsx +++ b/app/soapbox/features/account-gallery/index.tsx @@ -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 = ({ 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; diff --git a/app/soapbox/features/admin/components/registration-mode-picker.tsx b/app/soapbox/features/admin/components/registration-mode-picker.tsx index 5808ace63..2c830153a 100644 --- a/app/soapbox/features/admin/components/registration-mode-picker.tsx +++ b/app/soapbox/features/admin/components/registration-mode-picker.tsx @@ -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 = e => { const config = generateConfig(e.target.value as RegistrationMode); diff --git a/app/soapbox/features/admin/tabs/dashboard.tsx b/app/soapbox/features/admin/tabs/dashboard.tsx index 495f0ff37..e773228bb 100644 --- a/app/soapbox/features/admin/tabs/dashboard.tsx +++ b/app/soapbox/features/admin/tabs/dashboard.tsx @@ -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(); diff --git a/app/soapbox/features/ads/components/ad.tsx b/app/soapbox/features/ads/components/ad.tsx index 73446989d..0ffa28217 100644 --- a/app/soapbox/features/ads/components/ad.tsx +++ b/app/soapbox/features/ads/components/ad.tsx @@ -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 type { Ad as AdEntity } from 'soapbox/types/soapbox'; @@ -15,7 +15,7 @@ interface IAd { /** Displays an ad in sponsored post format. */ const Ad: React.FC = ({ ad }) => { const queryClient = useQueryClient(); - const instance = useAppSelector(state => state.instance); + const instance = useInstance(); const timer = useRef(undefined); const infobox = useRef(null); diff --git a/app/soapbox/features/aliases/components/account.tsx b/app/soapbox/features/aliases/components/account.tsx index 1687b85e6..701931837 100644 --- a/app/soapbox/features/aliases/components/account.tsx +++ b/app/soapbox/features/aliases/components/account.tsx @@ -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 = ({ 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!)); diff --git a/app/soapbox/features/aliases/index.tsx b/app/soapbox/features/aliases/index.tsx index cf5ee3e16..54e0d254d 100644 --- a/app/soapbox/features/aliases/index.tsx +++ b/app/soapbox/features/aliases/index.tsx @@ -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; + const searchAccountIds = useAppSelector((state) => state.aliases.suggestions.items); const loaded = useAppSelector((state) => state.aliases.suggestions.loaded); diff --git a/app/soapbox/features/auth-layout/index.tsx b/app/soapbox/features/auth-layout/index.tsx index b4f7ee12b..629bb3c9b 100644 --- a/app/soapbox/features/auth-layout/index.tsx +++ b/app/soapbox/features/auth-layout/index.tsx @@ -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 = () => {
- +
diff --git a/app/soapbox/features/auth-login/components/consumers-list.tsx b/app/soapbox/features/auth-login/components/consumers-list.tsx index 7136d08e4..95f9e6b3e 100644 --- a/app/soapbox/features/auth-login/components/consumers-list.tsx +++ b/app/soapbox/features/auth-login/components/consumers-list.tsx @@ -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 = () => { - const providers = useAppSelector(state => ImmutableList(state.instance.pleroma.get('oauth_consumer_strategies'))); + const instance = useInstance(); + const providers = ImmutableList(instance.pleroma.get('oauth_consumer_strategies')); if (providers.size > 0) { return ( diff --git a/app/soapbox/features/auth-login/components/registration-form.tsx b/app/soapbox/features/auth-login/components/registration-form.tsx index edd55deb3..0f825ea8c 100644 --- a/app/soapbox/features/auth-login/components/registration-form.tsx +++ b/app/soapbox/features/auth-login/components/registration-form.tsx @@ -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 = ({ 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']); diff --git a/app/soapbox/features/compose/components/compose-form.tsx b/app/soapbox/features/compose/components/compose-form.tsx index 1267c8753..01f16d96e 100644 --- a/app/soapbox/features/compose/components/compose-form.tsx +++ b/app/soapbox/features/compose/components/compose-form.tsx @@ -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, 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(); diff --git a/app/soapbox/features/compose/components/polls/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx index 77f47bdfc..5780cab1b 100644 --- a/app/soapbox/features/compose/components/polls/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -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 = ({ 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; 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)); diff --git a/app/soapbox/features/compose/components/reply-mentions.tsx b/app/soapbox/features/compose/components/reply-mentions.tsx index 865f6539d..e9d5c8d57 100644 --- a/app/soapbox/features/compose/components/reply-mentions.tsx +++ b/app/soapbox/features/compose/components/reply-mentions.tsx @@ -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 = ({ 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(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; } diff --git a/app/soapbox/features/compose/components/upload-button.tsx b/app/soapbox/features/compose/components/upload-button.tsx index aa611bf75..8623ee2ae 100644 --- a/app/soapbox/features/compose/components/upload-button.tsx +++ b/app/soapbox/features/compose/components/upload-button.tsx @@ -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 = ({ resetFileKey, }) => { const intl = useIntl(); + const { configuration } = useInstance(); const fileElement = useRef(null); - const attachmentTypes = useAppSelector(state => state.instance.configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList); + const attachmentTypes = configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList; const handleChange: React.ChangeEventHandler = (e) => { if (e.target.files?.length) { diff --git a/app/soapbox/features/compose/components/upload.tsx b/app/soapbox/features/compose/components/upload.tsx index 769efcdd1..030e00fbe 100644 --- a/app/soapbox/features/compose/components/upload.tsx +++ b/app/soapbox/features/compose/components/upload.tsx @@ -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 = ({ 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); diff --git a/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx b/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx index 3151738df..7105fdf28 100644 --- a/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx +++ b/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx @@ -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 = ({ 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 = ({ limit = 3 }): JSX.Ele diff --git a/app/soapbox/features/crypto-donate/index.tsx b/app/soapbox/features/crypto-donate/index.tsx index 820124b9d..f2affe4bd 100644 --- a/app/soapbox/features/crypto-donate/index.tsx +++ b/app/soapbox/features/crypto-donate/index.tsx @@ -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 ( @@ -26,7 +27,7 @@ const CryptoDonate: React.FC = (): JSX.Element => { diff --git a/app/soapbox/features/directory/index.tsx b/app/soapbox/features/directory/index.tsx index 1266ed0ab..70d227868 100644 --- a/app/soapbox/features/directory/index.tsx +++ b/app/soapbox/features/directory/index.tsx @@ -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 = () => {
Fediverse filter
- +
diff --git a/app/soapbox/features/edit-profile/index.tsx b/app/soapbox/features/edit-profile/index.tsx index 2f77e1142..97fdd7e50 100644 --- a/app/soapbox/features/edit-profile/index.tsx +++ b/app/soapbox/features/edit-profile/index.tsx @@ -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 = ({ 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({}); diff --git a/app/soapbox/features/federation-restrictions/components/instance-restrictions.tsx b/app/soapbox/features/federation-restrictions/components/instance-restrictions.tsx index ac86039fd..4fe3c04fd 100644 --- a/app/soapbox/features/federation-restrictions/components/instance-restrictions.tsx +++ b/app/soapbox/features/federation-restrictions/components/instance-restrictions.tsx @@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl'; import Icon from 'soapbox/components/icon'; import { Text } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useInstance } from 'soapbox/hooks'; import type { Map as ImmutableMap } from 'immutable'; @@ -21,7 +21,7 @@ interface IInstanceRestrictions { } const InstanceRestrictions: React.FC = ({ remoteInstance }) => { - const instance = useAppSelector(state => state.instance); + const instance = useInstance(); const renderRestrictions = () => { const items = []; diff --git a/app/soapbox/features/federation-restrictions/index.tsx b/app/soapbox/features/federation-restrictions/index.tsx index fae7994cb..f0b1a3ae2 100644 --- a/app/soapbox/features/federation-restrictions/index.tsx +++ b/app/soapbox/features/federation-restrictions/index.tsx @@ -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; 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 })}
- + {hosts.map((host) => )}
diff --git a/app/soapbox/features/home-timeline/index.tsx b/app/soapbox/features/home-timeline/index.tsx index 8c5522c66..83bdf6c9d 100644 --- a/app/soapbox/features/home-timeline/index.tsx +++ b/app/soapbox/features/home-timeline/index.tsx @@ -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(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 = () => { @@ -116,7 +116,7 @@ const HomeTimeline: React.FC = () => { values={{ public: ( - + ), }} diff --git a/app/soapbox/features/landing-page/index.tsx b/app/soapbox/features/landing-page/index.tsx index b508ff2d9..b7a0d418c 100644 --- a/app/soapbox/features/landing-page/index.tsx +++ b/app/soapbox/features/landing-page/index.tsx @@ -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 */ diff --git a/app/soapbox/features/migration/index.tsx b/app/soapbox/features/migration/index.tsx index d84e05576..edf8ea6f7 100644 --- a/app/soapbox/features/migration/index.tsx +++ b/app/soapbox/features/migration/index.tsx @@ -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(''); diff --git a/app/soapbox/features/notifications/components/notification.tsx b/app/soapbox/features/notifications/components/notification.tsx index b50d953c8..97f38a7bf 100644 --- a/app/soapbox/features/notifications/components/notification.tsx +++ b/app/soapbox/features/notifications/components/notification.tsx @@ -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 = (props) => { const history = useHistory(); const intl = useIntl(); - const instance = useAppSelector((state) => state.instance); + const instance = useInstance(); const type = notification.type; const { account, status } = notification; diff --git a/app/soapbox/features/onboarding/steps/fediverse-step.tsx b/app/soapbox/features/onboarding/steps/fediverse-step.tsx index 4d83c5b98..83a5795eb 100644 --- a/app/soapbox/features/onboarding/steps/fediverse-step.tsx +++ b/app/soapbox/features/onboarding/steps/fediverse-step.tsx @@ -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 ( @@ -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, }} /> @@ -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, }} /> diff --git a/app/soapbox/features/public-layout/components/header.tsx b/app/soapbox/features/public-layout/components/header.tsx index 053711afd..8bd12d4fd 100644 --- a/app/soapbox/features/public-layout/components/header.tsx +++ b/app/soapbox/features/public-layout/components/header.tsx @@ -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'; @@ -33,7 +33,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); diff --git a/app/soapbox/features/public-timeline/index.tsx b/app/soapbox/features/public-timeline/index.tsx index 6fec372ce..2515877f2 100644 --- a/app/soapbox/features/public-timeline/index.tsx +++ b/app/soapbox/features/public-timeline/index.tsx @@ -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: ( ), diff --git a/app/soapbox/features/register-invite/index.tsx b/app/soapbox/features/register-invite/index.tsx index c97469011..c1f21f0ea 100644 --- a/app/soapbox/features/register-invite/index.tsx +++ b/app/soapbox/features/register-invite/index.tsx @@ -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(); - const siteTitle = useAppSelector(state => state.instance.title); const title = ( ); diff --git a/app/soapbox/features/server-info/index.tsx b/app/soapbox/features/server-info/index.tsx index 94619f72f..bc0b03e9b 100644 --- a/app/soapbox/features/server-info/index.tsx +++ b/app/soapbox/features/server-info/index.tsx @@ -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 ( diff --git a/app/soapbox/features/settings/index.tsx b/app/soapbox/features/settings/index.tsx index 88ec78def..f6bc4c37e 100644 --- a/app/soapbox/features/settings/index.tsx +++ b/app/soapbox/features/settings/index.tsx @@ -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'; @@ -36,7 +35,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'); diff --git a/app/soapbox/features/status/components/thread-login-cta.tsx b/app/soapbox/features/status/components/thread-login-cta.tsx index e73a8e18b..26462cd20 100644 --- a/app/soapbox/features/status/components/thread-login-cta.tsx +++ b/app/soapbox/features/status/components/thread-login-cta.tsx @@ -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 = () => { diff --git a/app/soapbox/features/ui/components/cta-banner.tsx b/app/soapbox/features/ui/components/cta-banner.tsx index fa4ceba90..87860b318 100644 --- a/app/soapbox/features/ui/components/cta-banner.tsx +++ b/app/soapbox/features/ui/components/cta-banner.tsx @@ -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 = () => { - + diff --git a/app/soapbox/features/ui/components/modals/hotkeys-modal.tsx b/app/soapbox/features/ui/components/modals/hotkeys-modal.tsx index 1f44c9f9a..870ed8736 100644 --- a/app/soapbox/features/ui/components/modals/hotkeys-modal.tsx +++ b/app/soapbox/features/ui/components/modals/hotkeys-modal.tsx @@ -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 = ({ onClose }) => { - const features = useAppSelector((state) => getFeatures(state.instance)); + const features = useFeatures(); return ( = ({ 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; diff --git a/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx b/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx index ec6865b23..b36547eae 100644 --- a/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx +++ b/app/soapbox/features/ui/components/modals/unauthorized-modal.tsx @@ -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 = ({ 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 = ({ action, onClose, acco
{!singleUserMode && ( - + )} @@ -135,7 +135,7 @@ const UnauthorizedModal: React.FC = ({ action, onClose, acco return ( } + title={} onClose={onClickClose} confirmationAction={onLogin} confirmationText={} diff --git a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx index 6441e8763..31edfba4a 100644 --- a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx +++ b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx @@ -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 = ({ 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.IDLE); @@ -143,7 +143,7 @@ const VerifySmsModal: React.FC = ({ onClose }) => { id='sms_verification.modal.verify_help_text' defaultMessage='Verify your phone number to start using {instance}.' values={{ - instance: title, + instance: instance.title, }} /> diff --git a/app/soapbox/features/ui/components/panels/sign-up-panel.tsx b/app/soapbox/features/ui/components/panels/sign-up-panel.tsx index cafcdc5c4..318080770 100644 --- a/app/soapbox/features/ui/components/panels/sign-up-panel.tsx +++ b/app/soapbox/features/ui/components/panels/sign-up-panel.tsx @@ -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 = () => { - + diff --git a/app/soapbox/features/ui/components/promo-panel.tsx b/app/soapbox/features/ui/components/promo-panel.tsx index 9add33b27..12798dc06 100644 --- a/app/soapbox/features/ui/components/promo-panel.tsx +++ b/app/soapbox/features/ui/components/promo-panel.tsx @@ -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 ( - + {promoItems.map((item, i) => ( diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 019c14997..c280aafb1 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -25,18 +25,15 @@ 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 { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useInstance } from 'soapbox/hooks'; import AdminPage from 'soapbox/pages/admin-page'; import DefaultPage from 'soapbox/pages/default-page'; -// import GroupsPage from 'soapbox/pages/groups_page'; -// import GroupPage from 'soapbox/pages/group_page'; import HomePage from 'soapbox/pages/home-page'; import ProfilePage from 'soapbox/pages/profile-page'; import RemoteInstancePage from 'soapbox/pages/remote-instance-page'; import StatusPage from 'soapbox/pages/status-page'; import { getAccessToken, getVapidKey } from 'soapbox/utils/auth'; import { isStandalone } from 'soapbox/utils/state'; -// import GroupSidebarPanel from '../groups/sidebar_panel'; import BackgroundShapes from './components/background-shapes'; import Navbar from './components/navbar'; @@ -190,18 +187,6 @@ const SwitchingColumnsArea: React.FC = ({ children }) => { )} - {/* Gab groups */} - {/* - - - - - - - - - */} - {/* Mastodon web routes */} @@ -331,6 +316,7 @@ const UI: React.FC = ({ children }) => { const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); + const instance = useInstance(); const [draggingOver, setDraggingOver] = useState(false); const [mobile, setMobile] = useState(isMobile(window.innerWidth)); @@ -347,7 +333,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) => { diff --git a/app/soapbox/features/verification/registration.tsx b/app/soapbox/features/verification/registration.tsx index e409f3beb..a8e391459 100644 --- a/app/soapbox/features/verification/registration.tsx +++ b/app/soapbox/features/verification/registration.tsx @@ -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'; @@ -42,11 +42,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(false); @@ -66,7 +66,7 @@ const Registration = () => { dispatch(startOnboarding()); dispatch( snackbar.success( - intl.formatMessage(messages.success, { siteTitle }), + intl.formatMessage(messages.success, { siteTitle: instance.title }), ), ); }) diff --git a/app/soapbox/features/verification/steps/age-verification.tsx b/app/soapbox/features/verification/steps/age-verification.tsx index 84283f4b4..afaefbd89 100644 --- a/app/soapbox/features/verification/steps/age-verification.tsx +++ b/app/soapbox/features/verification/steps/age-verification.tsx @@ -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'; @@ -61,7 +61,7 @@ const AgeVerification = () => { - {siteTitle} requires users to be at least {ageMinimum} years old to + {instance.title} 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. diff --git a/app/soapbox/features/verification/waitlist-page.tsx b/app/soapbox/features/verification/waitlist-page.tsx index 1ae44817c..066c6135a 100644 --- a/app/soapbox/features/verification/waitlist-page.tsx +++ b/app/soapbox/features/verification/waitlist-page.tsx @@ -7,11 +7,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'); @@ -55,7 +55,7 @@ const WaitlistPage = (/* { account } */) => { - Welcome back to {title}! You were previously placed on our + Welcome back to {instance.title}! You were previously placed on our waitlist. Please verify your phone number to receive immediate access to your account! diff --git a/app/soapbox/hooks/index.ts b/app/soapbox/hooks/index.ts index 32413dd8f..41f1ebfd4 100644 --- a/app/soapbox/hooks/index.ts +++ b/app/soapbox/hooks/index.ts @@ -5,6 +5,7 @@ export { useAppSelector } from './useAppSelector'; export { useCompose } from './useCompose'; export { useDimensions } from './useDimensions'; export { useFeatures } from './useFeatures'; +export { useInstance } from './useInstance'; export { useLocale } from './useLocale'; export { useOnScreen } from './useOnScreen'; export { useOwnAccount } from './useOwnAccount'; diff --git a/app/soapbox/hooks/useFeatures.ts b/app/soapbox/hooks/useFeatures.ts index 1fefa6bce..bd0929bf5 100644 --- a/app/soapbox/hooks/useFeatures.ts +++ b/app/soapbox/hooks/useFeatures.ts @@ -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); }; diff --git a/app/soapbox/hooks/useInstance.ts b/app/soapbox/hooks/useInstance.ts new file mode 100644 index 000000000..1a6c18362 --- /dev/null +++ b/app/soapbox/hooks/useInstance.ts @@ -0,0 +1,6 @@ +import { useAppSelector } from 'soapbox/hooks'; + +/** Get the Instance for the current backend. */ +export const useInstance = () => { + return useAppSelector((state) => state.instance); +}; diff --git a/app/soapbox/hooks/useOwnAccount.ts b/app/soapbox/hooks/useOwnAccount.ts index ea541dfee..15c6a83af 100644 --- a/app/soapbox/hooks/useOwnAccount.ts +++ b/app/soapbox/hooks/useOwnAccount.ts @@ -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; + } + }); }; From b556166efa1a2c68320f2afd3d7a50028d384841 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Nov 2022 15:10:10 -0600 Subject: [PATCH 6/7] StatusInteractionBar: combine likes and reactions again --- .../status/components/status-interaction-bar.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/soapbox/features/status/components/status-interaction-bar.tsx b/app/soapbox/features/status/components/status-interaction-bar.tsx index fa4ff86ea..f888cb059 100644 --- a/app/soapbox/features/status/components/status-interaction-bar.tsx +++ b/app/soapbox/features/status/components/status-interaction-bar.tsx @@ -52,10 +52,10 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. const getNormalizedReacts = () => { return reduceEmoji( ImmutableList(status.pleroma.get('emoji_reactions') as any), - 0, - false, + status.favourites_count, + status.favourited, allowedEmoji, - ).reverse(); + ); }; const handleOpenReblogsModal: React.EventHandler = (e) => { @@ -142,8 +142,7 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. return ( {getReposts()} - {getFavourites()} - {features.emojiReacts && getEmojiReacts()} + {features.emojiReacts ? getEmojiReacts() : getFavourites()} ); }; From 7cce9812ef78d305072eec819ccb6f0580a1b6e9 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 28 Nov 2022 09:05:13 -0500 Subject: [PATCH 7/7] Add dropdown-menu to SensitiveContentOverlay to allow status deletion --- .../sensitive-content-overlay.test.tsx | 10 ++++ .../statuses/sensitive-content-overlay.tsx | 56 ++++++++++++++++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/app/soapbox/components/statuses/__tests__/sensitive-content-overlay.test.tsx b/app/soapbox/components/statuses/__tests__/sensitive-content-overlay.test.tsx index 4b2823840..ad677856e 100644 --- a/app/soapbox/components/statuses/__tests__/sensitive-content-overlay.test.tsx +++ b/app/soapbox/components/statuses/__tests__/sensitive-content-overlay.test.tsx @@ -20,6 +20,11 @@ describe('', () => { expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Sensitive content'); }); + it('does not allow user to delete the status', () => { + render(); + expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); + }); + it('can be toggled', () => { render(); @@ -43,6 +48,11 @@ describe('', () => { expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Content Under Review'); }); + it('allows the user to delete the status', () => { + render(); + expect(screen.getByTestId('icon-button')).toBeInTheDocument(); + }); + it('can be toggled', () => { render(); diff --git a/app/soapbox/components/statuses/sensitive-content-overlay.tsx b/app/soapbox/components/statuses/sensitive-content-overlay.tsx index 59faef161..21630929f 100644 --- a/app/soapbox/components/statuses/sensitive-content-overlay.tsx +++ b/app/soapbox/components/statuses/sensitive-content-overlay.tsx @@ -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((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(defaultMediaVisibility(status, displayMedia)); const toggleVisibility = (event: React.MouseEvent) => { @@ -48,6 +57,32 @@ const SensitiveContentOverlay = React.forwardRef { + 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 {intl.formatMessage(messages.show)} + + {(isUnderReview && isOwnStatus) ? ( + + ) : null}
)}