Rename pages to layouts

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-08-24 15:01:17 +02:00
parent 6ace7722a6
commit 7cc98b7293
52 changed files with 271 additions and 372 deletions

View file

@ -7,10 +7,7 @@ import { isLoggedIn } from 'soapbox/utils/auth';
import { getClient, type PlfeResponse } from '../api';
import {
importFetchedAccount,
importFetchedAccounts,
} from './importer';
import { importFetchedAccount, importFetchedAccounts } from './importer';
import type { Map as ImmutableMap } from 'immutable';
import type { MinifiedStatus } from 'soapbox/reducers/statuses';

View file

@ -1,9 +1,8 @@
import { type Account as BaseAccount, type Group, type Poll, type Status as BaseStatus } from 'pl-api';
import { importEntities } from 'soapbox/entity-store/actions';
import { Entities } from 'soapbox/entity-store/entities';
import { normalizeAccount, normalizeGroup } from 'soapbox/normalizers';
import type { Account as BaseAccount, Group, Poll, Status as BaseStatus } from 'pl-api';
import type { AppDispatch } from 'soapbox/store';
const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';

View file

@ -1,6 +1,5 @@
import IntlMessageFormat from 'intl-messageformat';
import 'intl-pluralrules';
import { type Account, type Notification as BaseNotification, type PaginatedResponse, type Status } from 'pl-api';
import { defineMessages } from 'react-intl';
import { getClient } from 'soapbox/api';
@ -23,6 +22,7 @@ import {
import { saveMarker } from './markers';
import { getSettings, saveSettings } from './settings';
import type { Account, Notification as BaseNotification, PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'soapbox/store';
const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE' as const;

View file

@ -1,11 +1,11 @@
import { type BookmarkFolder } from 'pl-api';
import { Entities } from 'soapbox/entity-store/entities';
import { selectEntity } from 'soapbox/entity-store/selectors';
import { useAppSelector } from 'soapbox/hooks';
import { useBookmarkFolders } from './useBookmarkFolders';
import type{ BookmarkFolder } from 'pl-api';
const useBookmarkFolder = (folderId?: string) => {
const {
isError,

View file

@ -3,10 +3,7 @@ import debounce from 'lodash/debounce';
import React, { useRef } from 'react';
import { fetchAccount } from 'soapbox/actions/accounts';
import {
openProfileHoverCard,
closeProfileHoverCard,
} from 'soapbox/actions/profile-hover-card';
import { openProfileHoverCard, closeProfileHoverCard } from 'soapbox/actions/profile-hover-card';
import { useAppDispatch } from 'soapbox/hooks';
import { isMobile } from 'soapbox/is-mobile';

View file

@ -3,10 +3,7 @@ import debounce from 'lodash/debounce';
import React, { useRef } from 'react';
import { useDispatch } from 'react-redux';
import {
openStatusHoverCard,
closeStatusHoverCard,
} from 'soapbox/actions/status-hover-card';
import { openStatusHoverCard, closeStatusHoverCard } from 'soapbox/actions/status-hover-card';
import { isMobile } from 'soapbox/is-mobile';
const showStatusHoverCard = debounce((dispatch, ref, statusId) => {

View file

@ -5,10 +5,7 @@ import { usePopper } from 'react-popper';
import { useHistory } from 'react-router-dom';
import { fetchRelationships } from 'soapbox/actions/accounts';
import {
closeProfileHoverCard,
updateProfileHoverCard,
} from 'soapbox/actions/profile-hover-card';
import { closeProfileHoverCard, updateProfileHoverCard } from 'soapbox/actions/profile-hover-card';
import { useAccount } from 'soapbox/api/hooks';
import Badge from 'soapbox/components/badge';
import ActionButton from 'soapbox/features/ui/components/action-button';

View file

@ -4,10 +4,7 @@ import { useIntl } from 'react-intl';
import { usePopper } from 'react-popper';
import { useHistory } from 'react-router-dom';
import {
closeStatusHoverCard,
updateStatusHoverCard,
} from 'soapbox/actions/status-hover-card';
import { closeStatusHoverCard, updateStatusHoverCard } from 'soapbox/actions/status-hover-card';
import { fetchStatus } from 'soapbox/actions/statuses';
import StatusContainer from 'soapbox/containers/status-container';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';

View file

@ -1,9 +1,10 @@
import { importEntities } from 'soapbox/entity-store/actions';
import { Entities } from 'soapbox/entity-store/entities';
import { type Entity } from 'soapbox/entity-store/types';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch';
import { useGetState } from 'soapbox/hooks/useGetState';
import type { Entity } from 'soapbox/entity-store/types';
type ChangeEntityFn<TEntity extends Entity> = (entity: TEntity) => TEntity
const useChangeEntity = <TEntity extends Entity = Entity>(entityType: Entities) => {

View file

@ -70,7 +70,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const controller = useRef(new AbortController());
const onInputChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> = e => {
setParams(params => ({ ...params, [e.target.name]: e.target.value }));
setParams(params => ({ ...params, [e.target.name]: e.target.value }));
};
const onUsernameChange: React.ChangeEventHandler<HTMLInputElement> = e => {

View file

@ -20,9 +20,7 @@ import {
$isHeadingNode,
HeadingTagType,
} from '@lexical/rich-text';
import {
$setBlocksType,
} from '@lexical/selection';
import { $setBlocksType } from '@lexical/selection';
import { $findMatchingParent, $getNearestNodeOfType, mergeRegister } from '@lexical/utils';
import clsx from 'clsx';
import {

View file

@ -1,6 +1,5 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $createRemarkExport } from '@mkljczk/lexical-remark';
import { type LanguageIdentificationModel } from 'fasttext.wasm.js/dist/models/language-identification/common.js';
import { $getRoot } from 'lexical';
import debounce from 'lodash/debounce';
import { useCallback, useEffect } from 'react';
@ -11,6 +10,8 @@ import { fetchStatus } from 'soapbox/actions/statuses';
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
import { getStatusIdsFromLinksInContent } from 'soapbox/utils/status';
import type { LanguageIdentificationModel } from 'fasttext.wasm.js/dist/models/language-identification/common.js';
let lidModel: LanguageIdentificationModel;
interface IStatePlugin {

View file

@ -3,10 +3,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
import { useHistory } from 'react-router-dom';
import {
setupMfa,
confirmMfa,
} from 'soapbox/actions/mfa';
import { setupMfa, confirmMfa } from 'soapbox/actions/mfa';
import { Button, Form, FormActions, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
import { useAppDispatch } from 'soapbox/hooks';
import toast from 'soapbox/toast';

View file

@ -4,7 +4,6 @@ import { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immuta
import React, { useEffect, useRef } from 'react';
import { useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { type VirtuosoHandle } from 'react-virtuoso';
import { type ComposeReplyAction, mentionCompose, replyCompose } from 'soapbox/actions/compose';
import { reblog, toggleFavourite, unreblog } from 'soapbox/actions/interactions';
@ -25,6 +24,7 @@ import { textForScreenReader } from 'soapbox/utils/status';
import DetailedStatus from './detailed-status';
import ThreadStatus from './thread-status';
import type { VirtuosoHandle } from 'react-virtuoso';
import type { Account, Status } from 'soapbox/normalizers';
import type { SelectedStatus } from 'soapbox/selectors';

View file

@ -10,7 +10,6 @@ import type { BaseModalProps } from '../modal-root';
import type { MediaAttachment } from 'pl-api';
import type { Account } from 'soapbox/normalizers';
type VideoModalProps = {
media: MediaAttachment;
statusId: string;

View file

@ -18,22 +18,22 @@ 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, useDraggedFiles, useInstance, useLoggedIn } from 'soapbox/hooks';
import AdminPage from 'soapbox/pages/admin-page';
import ChatsPage from 'soapbox/pages/chats-page';
import DefaultPage from 'soapbox/pages/default-page';
import EmptyPage from 'soapbox/pages/empty-page';
import EventPage from 'soapbox/pages/event-page';
import EventsPage from 'soapbox/pages/events-page';
import ExternalLoginPage from 'soapbox/pages/external-login-page';
import GroupPage from 'soapbox/pages/group-page';
import GroupsPage from 'soapbox/pages/groups-page';
import HomePage from 'soapbox/pages/home-page';
import LandingPage from 'soapbox/pages/landing-page';
import ManageGroupsPage from 'soapbox/pages/manage-groups-page';
import ProfilePage from 'soapbox/pages/profile-page';
import RemoteInstancePage from 'soapbox/pages/remote-instance-page';
import SearchPage from 'soapbox/pages/search-page';
import StatusPage from 'soapbox/pages/status-page';
import AdminLayout from 'soapbox/layouts/admin-layout';
import ChatsLayout from 'soapbox/layouts/chats-layout';
import DefaultLayout from 'soapbox/layouts/default-layout';
import EmptyLayout from 'soapbox/layouts/empty-layout';
import EventLayout from 'soapbox/layouts/event-layout';
import EventsLayout from 'soapbox/layouts/events-layout';
import ExternalLoginLayout from 'soapbox/layouts/external-login-layout';
import GroupLayout from 'soapbox/layouts/group-layout';
import GroupsLayout from 'soapbox/layouts/groups-layout';
import HomeLayout from 'soapbox/layouts/home-layout';
import LandingLayout from 'soapbox/layouts/landing-layout';
import ManageGroupsLayout from 'soapbox/layouts/manage-groups-layout';
import ProfileLayout from 'soapbox/layouts/profile-layout';
import RemoteInstanceLayout from 'soapbox/layouts/remote-instance-layout';
import SearchLayout from 'soapbox/layouts/search-layout';
import StatusLayout from 'soapbox/layouts/status-layout';
import { getVapidKey } from 'soapbox/utils/auth';
import { isStandalone } from 'soapbox/utils/state';
@ -160,24 +160,24 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<Switch>
{standalone && <Redirect from='/' to='/login/external' exact />}
<WrappedRoute path='/logout' page={EmptyPage} component={LogoutPage} publicRoute exact />
<WrappedRoute path='/logout' layout={EmptyLayout} component={LogoutPage} publicRoute exact />
{isLoggedIn ? (
<WrappedRoute path='/' exact page={HomePage} component={HomeTimeline} content={children} />
<WrappedRoute path='/' exact layout={HomeLayout} component={HomeTimeline} content={children} />
) : (
<WrappedRoute path='/' exact page={LandingPage} component={LandingTimeline} content={children} publicRoute />
<WrappedRoute path='/' exact layout={LandingLayout} component={LandingTimeline} content={children} publicRoute />
)}
{/*
NOTE: we cannot nest routes in a fragment
https://stackoverflow.com/a/68637108
*/}
{features.federating && <WrappedRoute path='/timeline/local' exact page={HomePage} component={CommunityTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/fediverse' exact page={HomePage} component={PublicTimeline} content={children} publicRoute />}
{features.bubbleTimeline && <WrappedRoute path='/timeline/bubble' exact page={HomePage} component={BubbleTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/:instance' exact page={RemoteInstancePage} component={RemoteTimeline} content={children} />}
{features.federating && <WrappedRoute path='/timeline/local' exact layout={HomeLayout} component={CommunityTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/fediverse' exact layout={HomeLayout} component={PublicTimeline} content={children} publicRoute />}
{features.bubbleTimeline && <WrappedRoute path='/timeline/bubble' exact layout={HomeLayout} component={BubbleTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/:instance' exact layout={RemoteInstanceLayout} component={RemoteTimeline} content={children} />}
{features.conversations && <WrappedRoute path='/conversations' page={DefaultPage} component={Conversations} content={children} />}
{features.conversations && <WrappedRoute path='/conversations' layout={DefaultLayout} component={Conversations} content={children} />}
{features.conversations && <Redirect from='/messages' to='/conversations' />}
{/* Mastodon web routes */}
@ -195,7 +195,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<Redirect from='/main/friends' to='/' />
<Redirect from='/tag/:id' to='/tags/:id' />
<Redirect from='/user-settings' to='/settings/profile' />
<WrappedRoute path='/notice/:statusId' publicRoute exact page={DefaultPage} component={Status} content={children} />
<WrappedRoute path='/notice/:statusId' publicRoute exact layout={DefaultLayout} component={Status} content={children} />
<Redirect from='/users/:username/statuses/:statusId' to='/@:username/posts/:statusId' />
<Redirect from='/users/:username/chats' to='/chats' />
<Redirect from='/users/:username' to='/@:username' />
@ -226,120 +226,120 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<Redirect from='/auth/password/new' to='/reset-password' />
<Redirect from='/auth/password/edit' to={`/edit-password${search}`} />
<WrappedRoute path='/tags/:id' publicRoute page={DefaultPage} component={HashtagTimeline} content={children} />
<WrappedRoute path='/tags/:id' publicRoute layout={DefaultLayout} component={HashtagTimeline} content={children} />
{features.lists && <WrappedRoute path='/lists' page={DefaultPage} component={Lists} content={children} />}
{features.lists && <WrappedRoute path='/list/:id' page={DefaultPage} component={ListTimeline} content={children} />}
{features.bookmarks && <WrappedRoute path='/bookmarks/all' page={DefaultPage} component={Bookmarks} content={children} />}
{features.bookmarks && <WrappedRoute path='/bookmarks/:id' page={DefaultPage} component={Bookmarks} content={children} />}
<WrappedRoute path='/bookmarks' page={DefaultPage} component={BookmarkFolders} content={children} />
{features.lists && <WrappedRoute path='/lists' layout={DefaultLayout} component={Lists} content={children} />}
{features.lists && <WrappedRoute path='/list/:id' layout={DefaultLayout} component={ListTimeline} content={children} />}
{features.bookmarks && <WrappedRoute path='/bookmarks/all' layout={DefaultLayout} component={Bookmarks} content={children} />}
{features.bookmarks && <WrappedRoute path='/bookmarks/:id' layout={DefaultLayout} component={Bookmarks} content={children} />}
<WrappedRoute path='/bookmarks' layout={DefaultLayout} component={BookmarkFolders} content={children} />
<WrappedRoute path='/notifications' page={DefaultPage} component={Notifications} content={children} />
<WrappedRoute path='/notifications' layout={DefaultLayout} component={Notifications} content={children} />
<WrappedRoute path='/search' page={SearchPage} component={Search} content={children} publicRoute />
{features.suggestions && <WrappedRoute path='/suggestions' publicRoute page={DefaultPage} component={FollowRecommendations} content={children} />}
{features.profileDirectory && <WrappedRoute path='/directory' publicRoute page={DefaultPage} component={Directory} content={children} />}
{features.events && <WrappedRoute path='/events' page={EventsPage} component={Events} content={children} />}
<WrappedRoute path='/search' layout={SearchLayout} component={Search} content={children} publicRoute />
{features.suggestions && <WrappedRoute path='/suggestions' publicRoute layout={DefaultLayout} component={FollowRecommendations} content={children} />}
{features.profileDirectory && <WrappedRoute path='/directory' publicRoute layout={DefaultLayout} component={Directory} content={children} />}
{features.events && <WrappedRoute path='/events' layout={EventsLayout} component={Events} content={children} />}
{features.chats && <WrappedRoute path='/chats' exact page={ChatsPage} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/new' page={ChatsPage} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/settings' page={ChatsPage} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/:chatId' page={ChatsPage} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats' exact layout={ChatsLayout} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/new' layout={ChatsLayout} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/settings' layout={ChatsLayout} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/:chatId' layout={ChatsLayout} component={ChatIndex} content={children} />}
<WrappedRoute path='/follow_requests' page={DefaultPage} component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' page={DefaultPage} component={Blocks} content={children} />
{features.federating && <WrappedRoute path='/domain_blocks' page={DefaultPage} component={DomainBlocks} content={children} />}
<WrappedRoute path='/mutes' page={DefaultPage} component={Mutes} content={children} />
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters/new' page={DefaultPage} component={EditFilter} content={children} />}
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters/:id' page={DefaultPage} component={EditFilter} content={children} />}
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters' page={DefaultPage} component={Filters} content={children} />}
{(features.followedHashtagsList) && <WrappedRoute path='/followed_tags' page={DefaultPage} component={FollowedTags} content={children} />}
<WrappedRoute path='/@:username' publicRoute exact component={AccountTimeline} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/with_replies' publicRoute={!authenticatedProfile} component={AccountTimeline} page={ProfilePage} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/@:username/followers' publicRoute={!authenticatedProfile} component={Followers} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/following' publicRoute={!authenticatedProfile} component={Following} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/media' publicRoute={!authenticatedProfile} component={AccountGallery} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/tagged/:tag' exact component={AccountTimeline} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/favorites' component={FavouritedStatuses} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/pins' component={PinnedStatuses} page={ProfilePage} content={children} />
<WrappedRoute path='/@:username/posts/:statusId' publicRoute exact page={StatusPage} component={Status} content={children} />
<WrappedRoute path='/@:username/posts/:statusId/quotes' publicRoute page={StatusPage} component={Quotes} content={children} />
{features.events && <WrappedRoute path='/@:username/events/:statusId' publicRoute exact page={EventPage} component={EventInformation} content={children} />}
{features.events && <WrappedRoute path='/@:username/events/:statusId/discussion' publicRoute exact page={EventPage} component={EventDiscussion} content={children} />}
<WrappedRoute path='/follow_requests' layout={DefaultLayout} component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' layout={DefaultLayout} component={Blocks} content={children} />
{features.federating && <WrappedRoute path='/domain_blocks' layout={DefaultLayout} component={DomainBlocks} content={children} />}
<WrappedRoute path='/mutes' layout={DefaultLayout} component={Mutes} content={children} />
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters/new' layout={DefaultLayout} component={EditFilter} content={children} />}
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters/:id' layout={DefaultLayout} component={EditFilter} content={children} />}
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters' layout={DefaultLayout} component={Filters} content={children} />}
{(features.followedHashtagsList) && <WrappedRoute path='/followed_tags' layout={DefaultLayout} component={FollowedTags} content={children} />}
<WrappedRoute path='/@:username' publicRoute exact layout={ProfileLayout} component={AccountTimeline} content={children} />
<WrappedRoute path='/@:username/with_replies' publicRoute={!authenticatedProfile} layout={ProfileLayout} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/@:username/followers' publicRoute={!authenticatedProfile} layout={ProfileLayout} component={Followers} content={children} />
<WrappedRoute path='/@:username/following' publicRoute={!authenticatedProfile} layout={ProfileLayout} component={Following} content={children} />
<WrappedRoute path='/@:username/media' publicRoute={!authenticatedProfile} layout={ProfileLayout} component={AccountGallery} content={children} />
<WrappedRoute path='/@:username/tagged/:tag' exact layout={ProfileLayout} component={AccountTimeline} content={children} />
<WrappedRoute path='/@:username/favorites' layout={ProfileLayout} component={FavouritedStatuses} content={children} />
<WrappedRoute path='/@:username/pins' layout={ProfileLayout} component={PinnedStatuses} content={children} />
<WrappedRoute path='/@:username/posts/:statusId' publicRoute exact layout={StatusLayout} component={Status} content={children} />
<WrappedRoute path='/@:username/posts/:statusId/quotes' publicRoute layout={StatusLayout} component={Quotes} content={children} />
{features.events && <WrappedRoute path='/@:username/events/:statusId' publicRoute exact layout={EventLayout} component={EventInformation} content={children} />}
{features.events && <WrappedRoute path='/@:username/events/:statusId/discussion' publicRoute exact layout={EventLayout} component={EventDiscussion} content={children} />}
<Redirect from='/@:username/:statusId' to='/@:username/posts/:statusId' />
<WrappedRoute path='/posts/:statusId' publicRoute exact page={DefaultPage} component={Status} content={children} />
<WrappedRoute path='/posts/:statusId' publicRoute exact layout={DefaultLayout} component={Status} content={children} />
{features.groups && <WrappedRoute path='/groups' exact page={GroupsPage} component={Groups} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId' exact page={GroupPage} component={GroupTimeline} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/members' exact page={GroupPage} component={GroupMembers} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/media' publicRoute={!authenticatedProfile} component={GroupGallery} page={GroupPage} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage' exact page={ManageGroupsPage} component={ManageGroup} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage/edit' exact page={ManageGroupsPage} component={EditGroup} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage/blocks' exact page={ManageGroupsPage} component={GroupBlockedMembers} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage/requests' exact page={ManageGroupsPage} component={GroupMembershipRequests} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/posts/:statusId' exact page={StatusPage} component={Status} content={children} />}
{features.groups && <WrappedRoute path='/groups' exact layout={GroupsLayout} component={Groups} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId' exact layout={GroupLayout} component={GroupTimeline} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/members' exact layout={GroupLayout} component={GroupMembers} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/media' publicRoute={!authenticatedProfile} layout={GroupLayout} component={GroupGallery} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage' exact layout={ManageGroupsLayout} component={ManageGroup} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage/edit' exact layout={ManageGroupsLayout} component={EditGroup} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage/blocks' exact layout={ManageGroupsLayout} component={GroupBlockedMembers} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/manage/requests' exact layout={ManageGroupsLayout} component={GroupMembershipRequests} content={children} />}
{features.groups && <WrappedRoute path='/groups/:groupId/posts/:statusId' exact layout={StatusLayout} component={Status} content={children} />}
<WrappedRoute path='/statuses/new' page={DefaultPage} component={NewStatus} content={children} exact />
<WrappedRoute path='/statuses/:statusId' exact page={StatusPage} component={Status} content={children} />
{features.scheduledStatuses && <WrappedRoute path='/scheduled_statuses' page={DefaultPage} component={ScheduledStatuses} content={children} />}
<WrappedRoute path='/draft_statuses' page={DefaultPage} component={DraftStatuses} content={children} />
<WrappedRoute path='/statuses/new' layout={DefaultLayout} component={NewStatus} content={children} exact />
<WrappedRoute path='/statuses/:statusId' exact layout={StatusLayout} component={Status} content={children} />
{features.scheduledStatuses && <WrappedRoute path='/scheduled_statuses' layout={DefaultLayout} component={ScheduledStatuses} content={children} />}
<WrappedRoute path='/draft_statuses' layout={DefaultLayout} component={DraftStatuses} content={children} />
<WrappedRoute path='/circle' page={DefaultPage} component={Circle} content={children} />
<WrappedRoute path='/circle' layout={DefaultLayout} component={Circle} content={children} />
<WrappedRoute path='/settings/profile' page={DefaultPage} component={EditProfile} content={children} />
{features.exportData && <WrappedRoute path='/settings/export' page={DefaultPage} component={ExportData} content={children} />}
{(features.importBlocks || features.importFollows || features.importMutes) && <WrappedRoute path='/settings/import' page={DefaultPage} component={ImportData} content={children} />}
{features.manageAccountAliases && <WrappedRoute path='/settings/aliases' page={DefaultPage} component={Aliases} content={children} />}
{features.accountMoving && <WrappedRoute path='/settings/migration' page={DefaultPage} component={Migration} content={children} />}
{features.accountBackups && <WrappedRoute path='/settings/backups' page={DefaultPage} component={Backups} content={children} />}
<WrappedRoute path='/settings/email' page={DefaultPage} component={EditEmail} content={children} />
<WrappedRoute path='/settings/password' page={DefaultPage} component={EditPassword} content={children} />
<WrappedRoute path='/settings/account' page={DefaultPage} component={DeleteAccount} content={children} />
<WrappedRoute path='/settings/mfa' page={DefaultPage} component={MfaForm} exact />
<WrappedRoute path='/settings/tokens' page={DefaultPage} component={AuthTokenList} content={children} />
<WrappedRoute path='/settings' page={DefaultPage} component={Settings} content={children} />
<WrappedRoute path='/soapbox/config' adminOnly page={DefaultPage} component={SoapboxConfig} content={children} />
<WrappedRoute path='/settings/profile' layout={DefaultLayout} component={EditProfile} content={children} />
{features.exportData && <WrappedRoute path='/settings/export' layout={DefaultLayout} component={ExportData} content={children} />}
{(features.importBlocks || features.importFollows || features.importMutes) && <WrappedRoute path='/settings/import' layout={DefaultLayout} component={ImportData} content={children} />}
{features.manageAccountAliases && <WrappedRoute path='/settings/aliases' layout={DefaultLayout} component={Aliases} content={children} />}
{features.accountMoving && <WrappedRoute path='/settings/migration' layout={DefaultLayout} component={Migration} content={children} />}
{features.accountBackups && <WrappedRoute path='/settings/backups' layout={DefaultLayout} component={Backups} content={children} />}
<WrappedRoute path='/settings/email' layout={DefaultLayout} component={EditEmail} content={children} />
<WrappedRoute path='/settings/password' layout={DefaultLayout} component={EditPassword} content={children} />
<WrappedRoute path='/settings/account' layout={DefaultLayout} component={DeleteAccount} content={children} />
<WrappedRoute path='/settings/mfa' layout={DefaultLayout} component={MfaForm} exact />
<WrappedRoute path='/settings/tokens' layout={DefaultLayout} component={AuthTokenList} content={children} />
<WrappedRoute path='/settings' layout={DefaultLayout} component={Settings} content={children} />
<WrappedRoute path='/soapbox/config' adminOnly layout={DefaultLayout} component={SoapboxConfig} content={children} />
<WrappedRoute path='/soapbox/admin' staffOnly page={AdminPage} component={Dashboard} content={children} exact />
<WrappedRoute path='/soapbox/admin/approval' staffOnly page={AdminPage} component={Dashboard} content={children} exact />
<WrappedRoute path='/soapbox/admin/reports' staffOnly page={AdminPage} component={Dashboard} content={children} exact />
<WrappedRoute path='/soapbox/admin/log' staffOnly page={AdminPage} component={ModerationLog} content={children} exact />
<WrappedRoute path='/soapbox/admin/users' staffOnly page={AdminPage} component={UserIndex} content={children} exact />
<WrappedRoute path='/soapbox/admin/theme' staffOnly page={AdminPage} component={ThemeEditor} content={children} exact />
<WrappedRoute path='/soapbox/admin/relays' staffOnly page={AdminPage} component={Relays} content={children} exact />
{features.adminAnnouncements && <WrappedRoute path='/soapbox/admin/announcements' staffOnly page={AdminPage} component={Announcements} content={children} exact />}
{features.domains && <WrappedRoute path='/soapbox/admin/domains' staffOnly page={AdminPage} component={Domains} content={children} exact />}
{features.adminRules && <WrappedRoute path='/soapbox/admin/rules' staffOnly page={AdminPage} component={Rules} content={children} exact />}
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
<WrappedRoute path='/soapbox/admin' staffOnly layout={AdminLayout} component={Dashboard} content={children} exact />
<WrappedRoute path='/soapbox/admin/approval' staffOnly layout={AdminLayout} component={Dashboard} content={children} exact />
<WrappedRoute path='/soapbox/admin/reports' staffOnly layout={AdminLayout} component={Dashboard} content={children} exact />
<WrappedRoute path='/soapbox/admin/log' staffOnly layout={AdminLayout} component={ModerationLog} content={children} exact />
<WrappedRoute path='/soapbox/admin/users' staffOnly layout={AdminLayout} component={UserIndex} content={children} exact />
<WrappedRoute path='/soapbox/admin/theme' staffOnly layout={AdminLayout} component={ThemeEditor} content={children} exact />
<WrappedRoute path='/soapbox/admin/relays' staffOnly layout={AdminLayout} component={Relays} content={children} exact />
{features.adminAnnouncements && <WrappedRoute path='/soapbox/admin/announcements' staffOnly layout={AdminLayout} component={Announcements} content={children} exact />}
{features.domains && <WrappedRoute path='/soapbox/admin/domains' staffOnly layout={AdminLayout} component={Domains} content={children} exact />}
{features.adminRules && <WrappedRoute path='/soapbox/admin/rules' staffOnly layout={AdminLayout} component={Rules} content={children} exact />}
<WrappedRoute path='/info' layout={EmptyLayout} component={ServerInfo} content={children} />
<WrappedRoute path='/developers/apps/create' developerOnly page={DefaultPage} component={CreateApp} content={children} />
<WrappedRoute path='/developers/settings_store' developerOnly page={DefaultPage} component={SettingsStore} content={children} />
<WrappedRoute path='/developers/timeline' developerOnly page={DefaultPage} component={TestTimeline} content={children} />
<WrappedRoute path='/developers/sw' developerOnly page={DefaultPage} component={ServiceWorkerInfo} content={children} />
<WrappedRoute path='/developers' page={DefaultPage} component={Developers} content={children} />
<WrappedRoute path='/error/network' developerOnly page={EmptyPage} component={lazy(() => Promise.reject(new TypeError('Failed to fetch dynamically imported module: TEST')))} content={children} />
<WrappedRoute path='/error' developerOnly page={EmptyPage} component={IntentionalError} content={children} />
<WrappedRoute path='/developers/apps/create' developerOnly layout={DefaultLayout} component={CreateApp} content={children} />
<WrappedRoute path='/developers/settings_store' developerOnly layout={DefaultLayout} component={SettingsStore} content={children} />
<WrappedRoute path='/developers/timeline' developerOnly layout={DefaultLayout} component={TestTimeline} content={children} />
<WrappedRoute path='/developers/sw' developerOnly layout={DefaultLayout} component={ServiceWorkerInfo} content={children} />
<WrappedRoute path='/developers' layout={DefaultLayout} component={Developers} content={children} />
<WrappedRoute path='/error/network' developerOnly layout={EmptyLayout} component={lazy(() => Promise.reject(new TypeError('Failed to fetch dynamically imported module: TEST')))} content={children} />
<WrappedRoute path='/error' developerOnly layout={EmptyLayout} component={IntentionalError} content={children} />
{hasCrypto && <WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />}
{features.federating && <WrappedRoute path='/federation_restrictions' publicRoute page={DefaultPage} component={FederationRestrictions} content={children} />}
{hasCrypto && <WrappedRoute path='/donate/crypto' publicRoute layout={DefaultLayout} component={CryptoDonate} content={children} />}
{features.federating && <WrappedRoute path='/federation_restrictions' publicRoute layout={DefaultLayout} component={FederationRestrictions} content={children} />}
<WrappedRoute path='/share' page={DefaultPage} component={Share} content={children} exact />
<WrappedRoute path='/share' layout={DefaultLayout} component={Share} content={children} exact />
<WrappedRoute path='/about/:slug?' page={DefaultPage} component={AboutPage} publicRoute exact />
<WrappedRoute path='/about/:slug?' layout={DefaultLayout} component={AboutPage} publicRoute exact />
{(features.accountCreation && instance.registrations.enabled) && (
<WrappedRoute path='/signup' page={EmptyPage} component={RegistrationPage} publicRoute exact />
<WrappedRoute path='/signup' layout={EmptyLayout} component={RegistrationPage} publicRoute exact />
)}
<WrappedRoute path='/login/external' page={ExternalLoginPage} component={ExternalLogin} publicRoute exact />
<WrappedRoute path='/login/add' page={DefaultPage} component={LoginPage} publicRoute exact />
<WrappedRoute path='/login' page={DefaultPage} component={LoginPage} publicRoute exact />
<WrappedRoute path='/reset-password' page={DefaultPage} component={PasswordReset} publicRoute exact />
<WrappedRoute path='/invite/:token' page={DefaultPage} component={RegisterInvite} publicRoute exact />
<WrappedRoute path='/login/external' layout={ExternalLoginLayout} component={ExternalLogin} publicRoute exact />
<WrappedRoute path='/login/add' layout={DefaultLayout} component={LoginPage} publicRoute exact />
<WrappedRoute path='/login' layout={DefaultLayout} component={LoginPage} publicRoute exact />
<WrappedRoute path='/reset-password' layout={DefaultLayout} component={PasswordReset} publicRoute exact />
<WrappedRoute path='/invite/:token' layout={DefaultLayout} component={RegisterInvite} publicRoute exact />
<Redirect from='/auth/password/new' to='/reset-password' />
<Redirect from='/auth/password/edit' to={`/edit-password${search}`} />
<WrappedRoute page={EmptyPage} component={GenericNotFound} content={children} />
<WrappedRoute layout={EmptyLayout} component={GenericNotFound} content={children} />
</Switch>
);
};

View file

@ -9,14 +9,14 @@ import ColumnForbidden from '../components/column-forbidden';
import ColumnLoading from '../components/column-loading';
import ErrorColumn from '../components/error-column';
type PageProps = {
type LayoutProps = {
params?: MatchType['params'];
children: React.ReactNode;
};
interface IWrappedRoute extends RouteProps {
component: React.LazyExoticComponent<any>;
page: React.ComponentType<PageProps>;
layout: React.ComponentType<LayoutProps>;
content?: React.ReactNode;
componentParams?: Record<string, any>;
publicRoute?: boolean;
@ -27,7 +27,7 @@ interface IWrappedRoute extends RouteProps {
const WrappedRoute: React.FC<IWrappedRoute> = ({
component: Component,
page: Page,
layout: Layout,
content,
componentParams = {},
publicRoute = false,
@ -44,11 +44,11 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
const renderComponent = ({ match }: RouteComponentProps) => (
<ErrorBoundary FallbackComponent={FallbackError}>
<Suspense fallback={<FallbackLoading />}>
<Page params={match.params} {...componentParams}>
<Layout params={match.params} {...componentParams}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</Page>
</Layout>
</Suspense>
</ErrorBoundary>
);

View file

@ -1,17 +1,15 @@
import React from 'react';
import { Layout } from 'soapbox/components/ui';
import {
LatestAccountsPanel,
} from 'soapbox/features/ui/util/async-components';
import { LatestAccountsPanel } from 'soapbox/features/ui/util/async-components';
import LinkFooter from '../features/ui/components/link-footer';
interface IAdminPage {
interface IAdminLayout {
children: React.ReactNode;
}
const AdminPage: React.FC<IAdminPage> = ({ children }) => (
const AdminLayout: React.FC<IAdminLayout> = ({ children }) => (
<>
<Layout.Main>
{children}
@ -24,4 +22,4 @@ const AdminPage: React.FC<IAdminPage> = ({ children }) => (
</>
);
export { AdminPage as default };
export { AdminLayout as default };

View file

@ -1,14 +1,14 @@
import React from 'react';
interface IChatsPage {
interface IChatsLayout {
children: React.ReactNode;
}
/** Custom layout for chats on desktop. */
const ChatsPage: React.FC<IChatsPage> = ({ children }) => (
const ChatsLayout: React.FC<IChatsLayout> = ({ children }) => (
<div className='black:border-gray-800 md:col-span-12 lg:col-span-9 lg:black:border-l'>
{children}
</div>
);
export { ChatsPage as default };
export { ChatsLayout as default };

View file

@ -11,11 +11,11 @@ import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { Layout } from '../components/ui';
interface IDefaultPage {
interface IDefaultLayout {
children: React.ReactNode;
}
const DefaultPage: React.FC<IDefaultPage> = ({ children }) => {
const DefaultLayout: React.FC<IDefaultLayout> = ({ children }) => {
const me = useAppSelector(state => state.me);
const features = useFeatures();
@ -45,4 +45,4 @@ const DefaultPage: React.FC<IDefaultPage> = ({ children }) => {
);
};
export { DefaultPage as default };
export { DefaultLayout as default };

View file

@ -2,11 +2,11 @@ import React from 'react';
import { Layout } from '../components/ui';
interface IEmptyPage {
interface IEmptyLayout {
children: React.ReactNode;
}
const EmptyPage: React.FC<IEmptyPage> = ({ children }) => (
const EmptyLayout: React.FC<IEmptyLayout> = ({ children }) => (
<>
<Layout.Main>
{children}
@ -16,4 +16,4 @@ const EmptyPage: React.FC<IEmptyPage> = ({ children }) => (
</>
);
export { EmptyPage as default };
export { EmptyLayout as default };

View file

@ -17,14 +17,14 @@ import { makeGetStatus } from 'soapbox/selectors';
const getStatus = makeGetStatus();
interface IEventPage {
interface IEventLayout {
params?: {
statusId?: string;
};
children: React.ReactNode;
}
const EventPage: React.FC<IEventPage> = ({ params, children }) => {
const EventLayout: React.FC<IEventLayout> = ({ params, children }) => {
const me = useAppSelector(state => state.me);
const features = useFeatures();
@ -96,4 +96,4 @@ const EventPage: React.FC<IEventPage> = ({ params, children }) => {
);
};
export { EventPage as default };
export { EventLayout as default };

View file

@ -9,12 +9,12 @@ import {
} from 'soapbox/features/ui/util/async-components';
import { useFeatures } from 'soapbox/hooks';
interface IEventsPage {
interface IEventsLayout {
children: React.ReactNode;
}
/** Page to display events list. */
const EventsPage: React.FC<IEventsPage> = ({ children }) => {
/** Layout to display events list. */
const EventsLayout: React.FC<IEventsLayout> = ({ children }) => {
const features = useFeatures();
return (
@ -37,4 +37,4 @@ const EventsPage: React.FC<IEventsPage> = ({ children }) => {
);
};
export { EventsPage as default };
export { EventsLayout as default };

View file

@ -12,11 +12,11 @@ import { isStandalone } from 'soapbox/utils/state';
import { Layout } from '../components/ui';
interface IExternalLoginPage {
interface IExternalLoginLayout {
children: React.ReactNode;
}
const ExternalLoginPage: React.FC<IExternalLoginPage> = ({ children }) => {
const ExternalLoginLayout: React.FC<IExternalLoginLayout> = ({ children }) => {
const me = useAppSelector(state => state.me);
const features = useFeatures();
const standalone = useAppSelector(isStandalone);
@ -47,4 +47,4 @@ const ExternalLoginPage: React.FC<IExternalLoginPage> = ({ children }) => {
);
};
export { ExternalLoginPage as default };
export { ExternalLoginLayout as default };

View file

@ -19,7 +19,7 @@ const messages = defineMessages({
media: { id: 'group.tabs.media', defaultMessage: 'Media' },
});
interface IGroupPage {
interface IGroupLayout {
params?: {
groupId?: string;
};
@ -44,8 +44,8 @@ const PrivacyBlankslate = () => (
</Stack>
);
/** Page to display a group. */
const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
/** Layout to display a group. */
const GroupLayout: React.FC<IGroupLayout> = ({ params, children }) => {
const intl = useIntl();
const match = useRouteMatch();
const { account: me } = useOwnAccount();
@ -122,4 +122,4 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
);
};
export { GroupPage as default };
export { GroupLayout as default };

View file

@ -4,12 +4,12 @@ import { Column, Layout } from 'soapbox/components/ui';
import LinkFooter from 'soapbox/features/ui/components/link-footer';
import { MyGroupsPanel, NewGroupPanel } from 'soapbox/features/ui/util/async-components';
interface IGroupsPage {
interface IGroupsLayout {
children: React.ReactNode;
}
/** Page to display groups. */
const GroupsPage: React.FC<IGroupsPage> = ({ children }) => (
/** Layout to display groups. */
const GroupsLayout: React.FC<IGroupsLayout> = ({ children }) => (
<>
<Layout.Main>
<Column withHeader={false}>
@ -28,4 +28,4 @@ const GroupsPage: React.FC<IGroupsPage> = ({ children }) => (
</>
);
export { GroupsPage as default };
export { GroupsLayout as default };

View file

@ -21,11 +21,11 @@ import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import { Avatar, Card, CardBody, HStack, Layout } from '../components/ui';
import ComposeForm from '../features/compose/components/compose-form';
interface IHomePage {
interface IHomeLayout {
children: React.ReactNode;
}
const HomePage: React.FC<IHomePage> = ({ children }) => {
const HomeLayout: React.FC<IHomeLayout> = ({ children }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
@ -114,4 +114,4 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
);
};
export { HomePage as default };
export { HomeLayout as default };

View file

@ -10,11 +10,11 @@ import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { Layout } from '../components/ui';
interface ILandingPage {
interface ILandingLayout {
children: React.ReactNode;
}
const LandingPage: React.FC<ILandingPage> = ({ children }) => {
const LandingLayout: React.FC<ILandingLayout> = ({ children }) => {
const me = useAppSelector(state => state.me);
const features = useFeatures();
@ -41,4 +41,4 @@ const LandingPage: React.FC<ILandingPage> = ({ children }) => {
);
};
export { LandingPage as default };
export { LandingLayout as default };

View file

@ -4,12 +4,12 @@ import { Layout } from 'soapbox/components/ui';
import LinkFooter from 'soapbox/features/ui/components/link-footer';
import { MyGroupsPanel, NewGroupPanel } from 'soapbox/features/ui/util/async-components';
interface IGroupsPage {
interface IGroupsLayout {
children: React.ReactNode;
}
/** Page to display groups. */
const ManageGroupsPage: React.FC<IGroupsPage> = ({ children }) => (
/** Layout to display groups. */
const ManageGroupsLayout: React.FC<IGroupsLayout> = ({ children }) => (
<>
<Layout.Main>
{children}
@ -23,4 +23,4 @@ const ManageGroupsPage: React.FC<IGroupsPage> = ({ children }) => (
</>
);
export { ManageGroupsPage as default };
export { ManageGroupsLayout as default };

View file

@ -19,15 +19,15 @@ import {
import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
import { getAcct } from 'soapbox/utils/accounts';
interface IProfilePage {
interface IProfileLayout {
params?: {
username?: string;
};
children: React.ReactNode;
}
/** Page to display a user's profile. */
const ProfilePage: React.FC<IProfilePage> = ({ params, children }) => {
/** Layout to display a user's profile. */
const ProfileLayout: React.FC<IProfileLayout> = ({ params, children }) => {
const history = useHistory();
const username = params?.username || '';
@ -129,4 +129,4 @@ const ProfilePage: React.FC<IProfilePage> = ({ params, children }) => {
);
};
export { ProfilePage as default };
export { ProfileLayout as default };

View file

@ -11,15 +11,15 @@ import { federationRestrictionsDisclosed } from 'soapbox/utils/state';
import { Layout } from '../components/ui';
interface IRemoteInstancePage {
interface IRemoteInstanceLayout {
params?: {
instance?: string;
};
children: React.ReactNode;
}
/** Page for viewing a remote instance timeline. */
const RemoteInstancePage: React.FC<IRemoteInstancePage> = ({ children, params }) => {
/** Layout for viewing a remote instance timeline. */
const RemoteInstanceLayout: React.FC<IRemoteInstanceLayout> = ({ children, params }) => {
const host = params!.instance!;
const { account } = useOwnAccount();
@ -43,4 +43,4 @@ const RemoteInstancePage: React.FC<IRemoteInstancePage> = ({ children, params })
);
};
export { RemoteInstancePage as default };
export { RemoteInstanceLayout as default };

View file

@ -11,11 +11,11 @@ import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { Layout } from '../components/ui';
interface ISearchPage {
interface ISearchLayout {
children: React.ReactNode;
}
const SearchPage: React.FC<ISearchPage> = ({ children }) => {
const SearchLayout: React.FC<ISearchLayout> = ({ children }) => {
const me = useAppSelector(state => state.me);
const features = useFeatures();
@ -48,4 +48,4 @@ const SearchPage: React.FC<ISearchPage> = ({ children }) => {
);
};
export { SearchPage as default };
export { SearchLayout as default };

View file

@ -11,11 +11,11 @@ import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { Layout } from '../components/ui';
interface IStatusPage {
interface IStatusLayout {
children: React.ReactNode;
}
const StatusPage: React.FC<IStatusPage> = ({ children }) => {
const StatusLayout: React.FC<IStatusLayout> = ({ children }) => {
const me = useAppSelector(state => state.me);
const features = useFeatures();
@ -45,4 +45,4 @@ const StatusPage: React.FC<IStatusPage> = ({ children }) => {
);
};
export { StatusPage as default };
export { StatusLayout as default };

View file

@ -1,4 +1,4 @@
import { type ChatMessage as BaseChatMessage } from 'pl-api';
import type { ChatMessage as BaseChatMessage } from 'pl-api';
const normalizeChatMessage = (chatMessage: BaseChatMessage & { pending?: boolean; deleting?: boolean }) => ({
type: 'message' as const,

View file

@ -1,9 +1,6 @@
import { Map as ImmutableMap } from 'immutable';
import {
BACKUPS_FETCH_SUCCESS,
BACKUPS_CREATE_SUCCESS,
} from '../actions/backups';
import { BACKUPS_FETCH_SUCCESS, BACKUPS_CREATE_SUCCESS } from '../actions/backups';
import type { Backup } from 'pl-api';
import type { AnyAction } from 'redux';

View file

@ -6,10 +6,7 @@ import {
import { STATUS_IMPORT, STATUSES_IMPORT } from 'soapbox/actions/importer';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
} from '../actions/accounts';
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from '../actions/accounts';
import {
CONTEXT_FETCH_SUCCESS,
STATUS_CREATE_REQUEST,

View file

@ -1,9 +1,6 @@
import { Record as ImmutableRecord } from 'immutable';
import {
DROPDOWN_MENU_OPEN,
DROPDOWN_MENU_CLOSE,
} from '../actions/dropdown-menu';
import { DROPDOWN_MENU_OPEN, DROPDOWN_MENU_CLOSE } from '../actions/dropdown-menu';
import type { AnyAction } from 'redux';

View file

@ -13,14 +13,8 @@ import {
ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS,
} from '../actions/accounts';
import {
DOMAIN_BLOCK_SUCCESS,
DOMAIN_UNBLOCK_SUCCESS,
} from '../actions/domain-blocks';
import {
ACCOUNT_IMPORT,
ACCOUNTS_IMPORT,
} from '../actions/importer';
import { DOMAIN_BLOCK_SUCCESS, DOMAIN_UNBLOCK_SUCCESS } from '../actions/domain-blocks';
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
import type { AnyAction } from 'redux';
import type { APIEntity } from 'soapbox/types/entities';

View file

@ -5,10 +5,7 @@ import {
MFA_CONFIRM_SUCCESS,
MFA_DISABLE_SUCCESS,
} from '../actions/mfa';
import {
FETCH_TOKENS_SUCCESS,
REVOKE_TOKEN_SUCCESS,
} from '../actions/security';
import { FETCH_TOKENS_SUCCESS, REVOKE_TOKEN_SUCCESS } from '../actions/security';
import type { OauthToken } from 'pl-api';
import type { AnyAction } from 'redux';

View file

@ -54,10 +54,7 @@ import {
UNPIN_SUCCESS,
type InteractionsAction,
} from '../actions/interactions';
import {
PINNED_STATUSES_FETCH_SUCCESS,
type PinStatusesAction,
} from '../actions/pin-statuses';
import { PINNED_STATUSES_FETCH_SUCCESS, type PinStatusesAction } from '../actions/pin-statuses';
import {
SCHEDULED_STATUSES_FETCH_REQUEST,
SCHEDULED_STATUSES_FETCH_SUCCESS,

View file

@ -4,10 +4,7 @@ import omit from 'lodash/omit';
import { normalizeStatus, normalizeTranslation, Status as StatusRecord } from 'soapbox/normalizers';
import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji-reacts';
import {
EMOJI_REACT_REQUEST,
UNEMOJI_REACT_REQUEST,
} from '../actions/emoji-reacts';
import { EMOJI_REACT_REQUEST, UNEMOJI_REACT_REQUEST } from '../actions/emoji-reacts';
import {
EVENT_JOIN_REQUEST,
EVENT_JOIN_FAIL,

View file

@ -6,15 +6,9 @@ import {
} from 'immutable';
import sample from 'lodash/sample';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
} from '../actions/accounts';
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from '../actions/accounts';
import { PIN_SUCCESS, UNPIN_SUCCESS } from '../actions/interactions';
import {
STATUS_CREATE_REQUEST,
STATUS_CREATE_SUCCESS,
} from '../actions/statuses';
import { STATUS_CREATE_REQUEST, STATUS_CREATE_SUCCESS } from '../actions/statuses';
import {
TIMELINE_UPDATE,
TIMELINE_DELETE,

View file

@ -1,9 +1,6 @@
import { OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord } from 'immutable';
import {
TRENDING_STATUSES_FETCH_REQUEST,
TRENDING_STATUSES_FETCH_SUCCESS,
} from 'soapbox/actions/trending-statuses';
import { TRENDING_STATUSES_FETCH_REQUEST, TRENDING_STATUSES_FETCH_SUCCESS } from 'soapbox/actions/trending-statuses';
import type { Status } from 'pl-api';
import type { AnyAction } from 'redux';

View file

@ -34,9 +34,7 @@ import {
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS,
EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS,
} from 'soapbox/actions/events';
import {
FAMILIAR_FOLLOWERS_FETCH_SUCCESS,
} from 'soapbox/actions/familiar-followers';
import { FAMILIAR_FOLLOWERS_FETCH_SUCCESS } from 'soapbox/actions/familiar-followers';
import {
GROUP_BLOCKS_FETCH_REQUEST,
GROUP_BLOCKS_FETCH_SUCCESS,
@ -51,9 +49,7 @@ import {
DISLIKES_FETCH_SUCCESS,
REACTIONS_FETCH_SUCCESS,
} from 'soapbox/actions/interactions';
import {
NOTIFICATIONS_UPDATE,
} from 'soapbox/actions/notifications';
import { NOTIFICATIONS_UPDATE } from 'soapbox/actions/notifications';
import type { Account, Notification, PaginatedResponse } from 'pl-api';
import type { APIEntity } from 'soapbox/types/entities';

View file

@ -9,7 +9,6 @@ import { createSelector } from 'reselect';
import { getLocale, getSettings } from 'soapbox/actions/settings';
import { Entities } from 'soapbox/entity-store/entities';
import { type MRFSimple } from 'soapbox/schemas/pleroma';
import { getDomain } from 'soapbox/utils/accounts';
import { validId } from 'soapbox/utils/auth';
import ConfigDB from 'soapbox/utils/config-db';
@ -20,6 +19,7 @@ import type { EntityStore } from 'soapbox/entity-store/types';
import type { Account, Group, Notification } from 'soapbox/normalizers';
import type { MinifiedNotification } from 'soapbox/reducers/notifications';
import type { MinifiedStatus } from 'soapbox/reducers/statuses';
import type { MRFSimple } from 'soapbox/schemas/pleroma';
import type { RootState } from 'soapbox/store';
const normalizeId = (id: any): string => typeof id === 'string' ? id : typeof id === 'object' ? normalizeId(id.id) : '';

View file

@ -1,7 +1,4 @@
import {
AdminAccountRecord,
AdminReportRecord,
} from 'soapbox/normalizers';
import { AdminAccountRecord, AdminReportRecord } from 'soapbox/normalizers';
type AdminAccount = ReturnType<typeof AdminAccountRecord>;
type AdminReport = ReturnType<typeof AdminReportRecord>;

View file

@ -24,23 +24,23 @@ const ALLOWED_EMOJI = ImmutableList([
describe('sortEmoji', () => {
describe('with an unsorted list of emoji', () => {
const emojiReacts = ImmutableList([
{ 'count': 7, 'me': true, 'name': '😃' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 3, 'me': true, 'name': '😢' },
{ 'count': 1, 'me': true, 'name': '😡' },
{ 'count': 7, 'me': true, 'name': '😃' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 3, 'me': true, 'name': '😢' },
{ 'count': 1, 'me': true, 'name': '😡' },
{ 'count': 20, 'me': true, 'name': '👍' },
{ 'count': 7, 'me': true, 'name': '😂' },
{ 'count': 7, 'me': true, 'name': '😂' },
{ 'count': 15, 'me': true, 'name': '❤' },
].map((react) => emojiReactionSchema.parse(react)));
it('sorts the emoji by count', () => {
expect(sortEmoji(emojiReacts, ALLOWED_EMOJI)).toEqual(fromJS([
{ 'count': 20, 'me': true, 'name': '👍' },
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 7, 'me': true, 'name': '😂' },
{ 'count': 7, 'me': true, 'name': '😃' },
{ 'count': 3, 'me': true, 'name': '😢' },
{ 'count': 1, 'me': true, 'name': '😡' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 7, 'me': true, 'name': '😂' },
{ 'count': 7, 'me': true, 'name': '😃' },
{ 'count': 3, 'me': true, 'name': '😢' },
{ 'count': 1, 'me': true, 'name': '😡' },
]));
});
});
@ -54,13 +54,13 @@ describe('mergeEmojiFavourites', () => {
const emojiReacts = ImmutableList([
{ 'count': 20, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 15, 'me': false, 'name': '❤', 'url': undefined },
{ 'count': 7, 'me': false, 'name': '😯', 'url': undefined },
{ 'count': 7, 'me': false, 'name': '😯', 'url': undefined },
].map((react) => emojiReactionSchema.parse(react)));
it('combines 👍 reacts with favourites', () => {
expect(mergeEmojiFavourites(emojiReacts, favouritesCount, favourited)).toEqual(fromJS([
{ 'count': 32, 'me': true, 'name': '👍', 'url': undefined },
{ 'count': 32, 'me': true, 'name': '👍', 'url': undefined },
{ 'count': 15, 'me': false, 'name': '❤', 'url': undefined },
{ 'count': 7, 'me': false, 'name': '😯', 'url': undefined },
{ 'count': 7, 'me': false, 'name': '😯', 'url': undefined },
]));
});
});
@ -68,19 +68,19 @@ describe('mergeEmojiFavourites', () => {
describe('without existing 👍 reacts', () => {
const emojiReacts = ImmutableList([
{ 'count': 15, 'me': false, 'name': '❤' },
{ 'count': 7, 'me': false, 'name': '😯' },
{ 'count': 7, 'me': false, 'name': '😯' },
].map((react) => emojiReactionSchema.parse(react)));
it('adds 👍 reacts to the map equaling favourite count', () => {
expect(mergeEmojiFavourites(emojiReacts, favouritesCount, favourited)).toEqual(fromJS([
{ 'count': 15, 'me': false, 'name': '❤' },
{ 'count': 7, 'me': false, 'name': '😯' },
{ 'count': 12, 'me': true, 'name': '👍' },
{ 'count': 7, 'me': false, 'name': '😯' },
{ 'count': 12, 'me': true, 'name': '👍' },
]));
});
it('does not add 👍 reacts when there are no favourites', () => {
expect(mergeEmojiFavourites(emojiReacts, 0, false)).toEqual(fromJS([
{ 'count': 15, 'me': false, 'name': '❤' },
{ 'count': 7, 'me': false, 'name': '😯' },
{ 'count': 15, 'me': false, 'name': '❤' },
{ 'count': 7, 'me': false, 'name': '😯' },
]));
});
});
@ -89,29 +89,29 @@ describe('mergeEmojiFavourites', () => {
describe('reduceEmoji', () => {
describe('with a clusterfuck of emoji', () => {
const emojiReacts = ImmutableList([
{ 'count': 1, 'me': false, 'name': '😡' },
{ 'count': 1, 'me': true, 'name': '🔪' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 3, 'me': false, 'name': '😢' },
{ 'count': 1, 'me': true, 'name': '🌵' },
{ 'count': 20, 'me': true, 'name': '👍' },
{ 'count': 7, 'me': false, 'name': '😂' },
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 1, 'me': false, 'name': '👀' },
{ 'count': 1, 'me': false, 'name': '🍩' },
{ 'count': 1, 'me': false, 'name': '😡' },
{ 'count': 1, 'me': true, 'name': '🔪' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 3, 'me': false, 'name': '😢' },
{ 'count': 1, 'me': true, 'name': '🌵' },
{ 'count': 20, 'me': true, 'name': '👍' },
{ 'count': 7, 'me': false, 'name': '😂' },
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 1, 'me': false, 'name': '👀' },
{ 'count': 1, 'me': false, 'name': '🍩' },
].map((react) => emojiReactionSchema.parse(react)));
it('sorts, filters, and combines emoji and favourites', () => {
expect(reduceEmoji(emojiReacts, 7, true, ALLOWED_EMOJI)).toEqual(fromJS([
{ 'count': 27, 'me': true, 'name': '👍' },
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 7, 'me': false, 'name': '😂' },
{ 'count': 3, 'me': false, 'name': '😢' },
{ 'count': 1, 'me': false, 'name': '😡' },
{ 'count': 1, 'me': true, 'name': '🔪' },
{ 'count': 1, 'me': true, 'name': '🌵' },
{ 'count': 1, 'me': false, 'name': '👀' },
{ 'count': 1, 'me': false, 'name': '🍩' },
{ 'count': 27, 'me': true, 'name': '👍' },
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 7, 'me': false, 'name': '😂' },
{ 'count': 3, 'me': false, 'name': '😢' },
{ 'count': 1, 'me': false, 'name': '😡' },
{ 'count': 1, 'me': true, 'name': '🔪' },
{ 'count': 1, 'me': true, 'name': '🌵' },
{ 'count': 1, 'me': false, 'name': '👀' },
{ 'count': 1, 'me': false, 'name': '🍩' },
]));
});
});
@ -124,9 +124,9 @@ describe('getReactForStatus', () => {
pleroma: {
emoji_reactions: [
{ 'count': 20, 'me': false, 'name': '👍' },
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 7, 'me': false, 'name': '😂' },
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 7, 'me': false, 'name': '😂' },
],
},
}));
@ -145,10 +145,10 @@ describe('getReactForStatus', () => {
it('returns undefined when a status has no valid reacts (or favourites)', () => {
const status = normalizeStatus(fromJS([
{ 'count': 1, 'me': true, 'name': '🔪' },
{ 'count': 1, 'me': true, 'name': '🌵' },
{ 'count': 1, 'me': false, 'name': '👀' },
{ 'count': 1, 'me': false, 'name': '🍩' },
{ 'count': 1, 'me': true, 'name': '🔪' },
{ 'count': 1, 'me': true, 'name': '🌵' },
{ 'count': 1, 'me': false, 'name': '👀' },
{ 'count': 1, 'me': false, 'name': '🍩' },
]));
expect(getReactForStatus(status)).toEqual(undefined);
});
@ -162,7 +162,7 @@ describe('simulateEmojiReact', () => {
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateEmojiReact(emojiReacts, '❤')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 3, 'me': true, 'name': '❤', 'url': undefined },
{ 'count': 3, 'me': true, 'name': '❤', 'url': undefined },
]));
});
@ -174,7 +174,7 @@ describe('simulateEmojiReact', () => {
expect(simulateEmojiReact(emojiReacts, '😯')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
{ 'count': 1, 'me': true, 'name': '😯', 'url': undefined },
{ 'count': 1, 'me': true, 'name': '😯', 'url': undefined },
]));
});
@ -185,8 +185,8 @@ describe('simulateEmojiReact', () => {
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateEmojiReact(emojiReacts, 'soapbox', 'https://gleasonator.com/emoji/Gleasonator/soapbox.png')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
{ 'count': 1, 'me': true, 'name': 'soapbox', 'url': 'https://gleasonator.com/emoji/Gleasonator/soapbox.png' },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
{ 'count': 1, 'me': true, 'name': 'soapbox', 'url': 'https://gleasonator.com/emoji/Gleasonator/soapbox.png' },
]));
});
});
@ -199,7 +199,7 @@ describe('simulateUnEmojiReact', () => {
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateUnEmojiReact(emojiReacts, '❤')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },
{ 'count': 2, 'me': false, 'name': '❤' },
]));
});
@ -207,7 +207,7 @@ describe('simulateUnEmojiReact', () => {
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },
{ 'count': 1, 'me': true, 'name': '😯' },
{ 'count': 1, 'me': true, 'name': '😯' },
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateUnEmojiReact(emojiReacts, '😯')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍' },
@ -219,7 +219,7 @@ describe('simulateUnEmojiReact', () => {
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },
{ 'count': 1, 'me': true, 'name': 'soapbox', 'url': 'https://gleasonator.com/emoji/Gleasonator/soapbox.png' },
{ 'count': 1, 'me': true, 'name': 'soapbox', 'url': 'https://gleasonator.com/emoji/Gleasonator/soapbox.png' },
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateUnEmojiReact(emojiReacts, 'soapbox')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍' },

View file

@ -96,4 +96,4 @@ const createFaviconService = () => {
const FaviconService = createFaviconService();
export { FaviconService as default };
export { checkCanvasExtractPermission, FaviconService as default };

View file

@ -1,4 +1,4 @@
import { type Notification } from 'pl-api';
import type { Notification } from 'pl-api';
/** Notification types known to Soapbox. */
const NOTIFICATION_TYPES = [

View file

@ -1,3 +1,5 @@
import { checkCanvasExtractPermission } from './favicon-service';
/* eslint-disable no-case-declarations */
const DEFAULT_MAX_PIXELS = 1920 * 1080;
@ -45,47 +47,6 @@ const dropOrientationIfNeeded = (orientation: number) => new Promise<number>(res
}
});
// /**
// *Some browsers don't allow reading from a canvas and instead return all-white
// * or randomized data. Use a pre-defined image to check if reading the canvas
// * works.
// */
// const checkCanvasReliability = () => new Promise<void>((resolve, reject) => {
// switch(_browser_quirks['canvas-read-unreliable']) {
// case true:
// reject('Canvas reading unreliable');
// break;
// case false:
// resolve();
// break;
// default:
// // 2×2 GIF with white, red, green and blue pixels
// const testImageURL =
// 'data:image/gif;base64,R0lGODdhAgACAKEDAAAA//8AAAD/AP///ywAAAAAAgACAAACA1wEBQA7';
// const refData =
// [255, 255, 255, 255, 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255];
// const img = new Image();
// img.onload = () => {
// const canvas = document.createElement('canvas');
// const context = canvas.getContext('2d');
// context?.drawImage(img, 0, 0, 2, 2);
// const imageData = context?.getImageData(0, 0, 2, 2);
// if (imageData?.data.every((x, i) => refData[i] === x)) {
// _browser_quirks['canvas-read-unreliable'] = false;
// resolve();
// } else {
// _browser_quirks['canvas-read-unreliable'] = true;
// reject('Canvas reading unreliable');
// }
// };
// img.onerror = () => {
// _browser_quirks['canvas-read-unreliable'] = true;
// reject('Failed to load test image');
// };
// img.src = testImageURL;
// }
// });
/** Convert the file into a local blob URL. */
const getImageUrl = (inputFile: File) => new Promise<string>((resolve, reject) => {
// @ts-ignore: This is a browser capabilities check.
@ -203,9 +164,8 @@ const resizeImage = (
const newWidth = Math.round(Math.sqrt(maxPixels * (width / height)));
const newHeight = Math.round(Math.sqrt(maxPixels * (height / width)));
// Skip canvas reliability check for now (it's unreliable)
// checkCanvasReliability()
// .then(getOrientation(img, type))
if (!checkCanvasExtractPermission()) return reject();
getOrientation(img, type)
.then(orientation => processImage(img, {
width: newWidth,

View file

@ -1,11 +1,12 @@
import aspectRatioPlugin from '@tailwindcss/aspect-ratio';
import formsPlugin from '@tailwindcss/forms';
import typographyPlugin from '@tailwindcss/typography';
import { type Config } from 'tailwindcss';
import plugin from 'tailwindcss/plugin';
import { parseColorMatrix } from './tailwind/colors';
import type { Config } from 'tailwindcss';
const blackVariantPlugin = plugin(({ addVariant }) => addVariant('black', '.black &'));
const reducedMotionPlugin = plugin(({ addVariant }) => addVariant('no-reduce-motion', '.no-reduce-motion &'));

View file

@ -1,4 +1,4 @@
import { type RecursiveKeyValuePair } from 'tailwindcss/types/config';
import type{ RecursiveKeyValuePair } from 'tailwindcss/types/config';
/** https://tailwindcss.com/docs/customizing-colors#using-css-variables */
const withOpacityValue = (variable: string): string => `rgb(var(${variable}) / <alpha-value>)`;