Merge remote-tracking branch 'soapbox/develop' into cleanup

This commit is contained in:
marcin mikołajczak
2022-12-31 19:52:03 +01:00
38 changed files with 636 additions and 7719 deletions

View File

@@ -1,3 +1,5 @@
import { getFeatures } from 'soapbox/utils/features';
import api, { getLinks } from '../api';
import type { AxiosError } from 'axios';
@@ -18,10 +20,17 @@ const SCHEDULED_STATUS_CANCEL_FAIL = 'SCHEDULED_STATUS_CANCEL_FAIL';
const fetchScheduledStatuses = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (getState().status_lists.get('scheduled_statuses')?.isLoading) {
const state = getState();
if (state.status_lists.get('scheduled_statuses')?.isLoading) {
return;
}
const instance = state.instance;
const features = getFeatures(instance);
if (!features.scheduledStatuses) return;
dispatch(fetchScheduledStatusesRequest());
api(getState).get('/api/v1/scheduled_statuses').then(response => {

View File

@@ -17,6 +17,8 @@ const fetchTrendingStatuses = () =>
const instance = state.instance;
const features = getFeatures(instance);
if (!features.trendingStatuses && !features.trendingTruths) return;
dispatch({ type: TRENDING_STATUSES_FETCH_REQUEST });
return api(getState).get(features.trendingTruths ? '/api/v1/truth/trending/truths' : '/api/v1/trends/statuses').then(({ data: statuses }) => {
dispatch(importFetchedStatuses(statuses));

View File

@@ -62,7 +62,6 @@ export const baseClient = (accessToken?: string | null, baseURL: string = ''): A
headers: Object.assign(accessToken ? {
'Authorization': `Bearer ${accessToken}`,
} : {}),
transformResponse: [maybeParseJSON],
});
};

View File

@@ -53,8 +53,7 @@ const AutosuggestAccountInput: React.FC<IAutosuggestAccountInput> = ({
setAccountIds(ImmutableOrderedSet(accountIds));
})
.catch(noOp);
}, 900, { leading: true, trailing: true }), [limit]);
}, 900, { leading: false, trailing: true }), [limit]);
const handleChange: React.ChangeEventHandler<HTMLInputElement> = e => {
refreshCancelToken();

View File

@@ -294,7 +294,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
const aspectRatio = media.getIn([0, 'meta', 'original', 'aspect']) as number | undefined;
const getHeight = () => {
if (!aspectRatio) return w;
if (!aspectRatio) return w * 9 / 16;
if (isPanoramic(aspectRatio)) return Math.floor(w / maximumAspectRatio);
if (isPortrait(aspectRatio)) return Math.floor(w / minimumAspectRatio);
return Math.floor(w / aspectRatio);

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/interactive-supports-focus */
import classNames from 'clsx';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
@@ -136,218 +137,234 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
return (
<div
className={classNames('sidebar-menu__root', {
'sidebar-menu__root--visible': sidebarOpen,
})}
aria-expanded={sidebarOpen}
className={
classNames({
'z-[1000]': sidebarOpen,
hidden: !sidebarOpen,
})
}
>
<div
className={classNames({
'fixed inset-0 bg-gray-500/90 dark:bg-gray-700/90 z-1000': true,
'hidden': !sidebarOpen,
})}
className='fixed inset-0 bg-gray-500/90 dark:bg-gray-700/90'
role='button'
onClick={handleClose}
>
<IconButton
title={intl.formatMessage(messages.close)}
onClick={handleClose}
src={require('@tabler/icons/x.svg')}
ref={closeButtonRef}
iconClassName='h-6 w-6'
className='fixed top-5 right-5 text-gray-600 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'
/>
</div>
/>
<div className='sidebar-menu'>
<div className='relative overflow-y-scroll overflow-auto h-full w-full'>
<div className='p-4'>
<Stack space={4}>
<Link to={`/@${account.acct}`} onClick={onClose}>
<Account account={account} showProfileHoverCard={false} withLinkToProfile={false} />
</Link>
<ProfileStats
account={account}
onClickHandler={handleClose}
/>
<div className='fixed inset-0 z-[1000] flex'>
<div
className={
classNames({
'flex flex-col flex-1 bg-white dark:bg-primary-900 -translate-x-full rtl:translate-x-full w-full max-w-xs': true,
'!translate-x-0': sidebarOpen,
})
}
>
<IconButton
title={intl.formatMessage(messages.close)}
onClick={handleClose}
src={require('@tabler/icons/x.svg')}
ref={closeButtonRef}
iconClassName='h-6 w-6'
className='absolute top-0 right-0 -mr-11 mt-2 text-gray-600 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'
/>
<div className='relative overflow-y-scroll overflow-auto h-full w-full'>
<div className='p-4'>
<Stack space={4}>
<Divider />
<Link to={`/@${account.acct}`} onClick={onClose}>
<Account account={account} showProfileHoverCard={false} withLinkToProfile={false} />
</Link>
<SidebarLink
to={`/@${account.acct}`}
icon={require('@tabler/icons/user.svg')}
text={intl.formatMessage(messages.profile)}
onClick={onClose}
<ProfileStats
account={account}
onClickHandler={handleClose}
/>
{(account.locked || followRequestsCount > 0) && (
<SidebarLink
to='/follow_requests'
icon={require('@tabler/icons/user-plus.svg')}
text={intl.formatMessage(messages.followRequests)}
onClick={onClose}
/>
)}
{features.bookmarks && (
<SidebarLink
to='/bookmarks'
icon={require('@tabler/icons/bookmark.svg')}
text={intl.formatMessage(messages.bookmarks)}
onClick={onClose}
/>
)}
{features.lists && (
<SidebarLink
to='/lists'
icon={require('@tabler/icons/list.svg')}
text={intl.formatMessage(messages.lists)}
onClick={onClose}
/>
)}
{features.events && (
<SidebarLink
to='/events'
icon={require('@tabler/icons/calendar-event.svg')}
text={intl.formatMessage(messages.events)}
onClick={onClose}
/>
)}
{settings.get('isDeveloper') && (
<SidebarLink
to='/developers'
icon={require('@tabler/icons/code.svg')}
text={intl.formatMessage(messages.developers)}
onClick={onClose}
/>
)}
{features.publicTimeline && <>
<Stack space={4}>
<Divider />
<SidebarLink
to='/timeline/local'
icon={features.federating ? require('@tabler/icons/affiliate.svg') : require('@tabler/icons/world.svg')}
text={features.federating ? <FormattedMessage id='tabs_bar.local' defaultMessage='Local' /> : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
to={`/@${account.acct}`}
icon={require('@tabler/icons/user.svg')}
text={intl.formatMessage(messages.profile)}
onClick={onClose}
/>
{(account.locked || followRequestsCount > 0) && (
<SidebarLink
to='/follow_requests'
icon={require('@tabler/icons/user-plus.svg')}
text={intl.formatMessage(messages.followRequests)}
onClick={onClose}
/>
)}
{features.bookmarks && (
<SidebarLink
to='/bookmarks'
icon={require('@tabler/icons/bookmark.svg')}
text={intl.formatMessage(messages.bookmarks)}
onClick={onClose}
/>
)}
{features.lists && (
<SidebarLink
to='/lists'
icon={require('@tabler/icons/list.svg')}
text={intl.formatMessage(messages.lists)}
onClick={onClose}
/>
)}
{features.events && (
<SidebarLink
to='/events'
icon={require('@tabler/icons/calendar-event.svg')}
text={intl.formatMessage(messages.events)}
onClick={onClose}
/>
)}
{settings.get('isDeveloper') && (
<SidebarLink
to='/developers'
icon={require('@tabler/icons/code.svg')}
text={intl.formatMessage(messages.developers)}
onClick={onClose}
/>
)}
{features.publicTimeline && <>
<Divider />
<SidebarLink
to='/timeline/local'
icon={features.federating ? require('@tabler/icons/affiliate.svg') : require('@tabler/icons/world.svg')}
text={features.federating ? <FormattedMessage id='tabs_bar.local' defaultMessage='Local' /> : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
onClick={onClose}
/>
{features.federating && (
<SidebarLink
to='/timeline/fediverse'
icon={require('@tabler/icons/topology-star-ring-3.svg')}
text={<FormattedMessage id='tabs_bar.fediverse' defaultMessage='Fediverse' />}
onClick={onClose}
/>
)}
</>}
<Divider />
<SidebarLink
to='/blocks'
icon={require('@tabler/icons/ban.svg')}
text={intl.formatMessage(messages.blocks)}
onClick={onClose}
/>
<SidebarLink
to='/mutes'
icon={require('@tabler/icons/circle-x.svg')}
text={intl.formatMessage(messages.mutes)}
onClick={onClose}
/>
<SidebarLink
to='/settings/preferences'
icon={require('@tabler/icons/settings.svg')}
text={intl.formatMessage(messages.preferences)}
onClick={onClose}
/>
{features.federating && (
<SidebarLink
to='/timeline/fediverse'
icon={require('@tabler/icons/topology-star-ring-3.svg')}
text={<FormattedMessage id='tabs_bar.fediverse' defaultMessage='Fediverse' />}
to='/domain_blocks'
icon={require('@tabler/icons/ban.svg')}
text={intl.formatMessage(messages.domainBlocks)}
onClick={onClose}
/>
)}
</>}
<Divider />
<SidebarLink
to='/blocks'
icon={require('@tabler/icons/ban.svg')}
text={intl.formatMessage(messages.blocks)}
onClick={onClose}
/>
<SidebarLink
to='/mutes'
icon={require('@tabler/icons/circle-x.svg')}
text={intl.formatMessage(messages.mutes)}
onClick={onClose}
/>
<SidebarLink
to='/settings/preferences'
icon={require('@tabler/icons/settings.svg')}
text={intl.formatMessage(messages.preferences)}
onClick={onClose}
/>
{features.federating && (
<SidebarLink
to='/domain_blocks'
icon={require('@tabler/icons/ban.svg')}
text={intl.formatMessage(messages.domainBlocks)}
onClick={onClose}
/>
)}
{features.filters && (
<SidebarLink
to='/filters'
icon={require('@tabler/icons/filter.svg')}
text={intl.formatMessage(messages.filters)}
onClick={onClose}
/>
)}
{account.admin && (
<SidebarLink
to='/soapbox/config'
icon={require('@tabler/icons/settings.svg')}
text={intl.formatMessage(messages.soapboxConfig)}
onClick={onClose}
/>
)}
{features.import && (
<SidebarLink
to='/settings/import'
icon={require('@tabler/icons/cloud-upload.svg')}
text={intl.formatMessage(messages.importData)}
onClick={onClose}
/>
)}
<Divider />
<SidebarLink
to='/logout'
icon={require('@tabler/icons/logout.svg')}
text={intl.formatMessage(messages.logout)}
onClick={onClickLogOut}
/>
<Divider />
<Stack space={4}>
<button type='button' onClick={handleSwitcherClick} className='py-1'>
<HStack alignItems='center' justifyContent='between'>
<Text tag='span'>
<FormattedMessage id='profile_dropdown.switch_account' defaultMessage='Switch accounts' />
</Text>
<Icon
src={require('@tabler/icons/chevron-down.svg')}
className={classNames('w-4 h-4 text-gray-900 dark:text-gray-100 transition-transform', {
'rotate-180': switcher,
})}
/>
</HStack>
</button>
{switcher && (
<div className='border-t-2 border-gray-100 dark:border-gray-800 border-solid'>
{otherAccounts.map(account => renderAccount(account))}
<NavLink className='flex items-center py-2 space-x-1' to='/login/add' onClick={handleClose}>
<Icon className='text-primary-500 w-4 h-4' src={require('@tabler/icons/plus.svg')} />
<Text size='sm' weight='medium'>{intl.formatMessage(messages.addAccount)}</Text>
</NavLink>
</div>
{features.filters && (
<SidebarLink
to='/filters'
icon={require('@tabler/icons/filter.svg')}
text={intl.formatMessage(messages.filters)}
onClick={onClose}
/>
)}
{account.admin && (
<SidebarLink
to='/soapbox/config'
icon={require('@tabler/icons/settings.svg')}
text={intl.formatMessage(messages.soapboxConfig)}
onClick={onClose}
/>
)}
{features.import && (
<SidebarLink
to='/settings/import'
icon={require('@tabler/icons/cloud-upload.svg')}
text={intl.formatMessage(messages.importData)}
onClick={onClose}
/>
)}
<Divider />
<SidebarLink
to='/logout'
icon={require('@tabler/icons/logout.svg')}
text={intl.formatMessage(messages.logout)}
onClick={onClickLogOut}
/>
<Divider />
<Stack space={4}>
<button type='button' onClick={handleSwitcherClick} className='py-1'>
<HStack alignItems='center' justifyContent='between'>
<Text tag='span'>
<FormattedMessage id='profile_dropdown.switch_account' defaultMessage='Switch accounts' />
</Text>
<Icon
src={require('@tabler/icons/chevron-down.svg')}
className={classNames('w-4 h-4 text-gray-900 dark:text-gray-100 transition-transform', {
'rotate-180': switcher,
})}
/>
</HStack>
</button>
{switcher && (
<div className='border-t-2 border-gray-100 dark:border-gray-800 border-solid'>
{otherAccounts.map(account => renderAccount(account))}
<NavLink className='flex items-center py-2 space-x-1' to='/login/add' onClick={handleClose}>
<Icon className='text-primary-500 w-4 h-4' src={require('@tabler/icons/plus.svg')} />
<Text size='sm' weight='medium'>{intl.formatMessage(messages.addAccount)}</Text>
</NavLink>
</div>
)}
</Stack>
</Stack>
</Stack>
</Stack>
</div>
</div>
</div>
{/* Dummy element to keep Close Icon visible */}
<div
aria-hidden
className='w-14 flex-shrink-0'
onClick={handleClose}
/>
</div>
</div>
);

View File

@@ -11,7 +11,7 @@ const Select = React.forwardRef<HTMLSelectElement, ISelect>((props, ref) => {
return (
<select
ref={ref}
className={`w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-800 focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:focus:ring-primary-500 dark:focus:border-primary-500 sm:text-sm rounded-md disabled:opacity-50 ${className}`}
className={`w-full pl-3 pr-10 py-2 text-base truncate border-gray-300 dark:border-gray-800 focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:focus:ring-primary-500 dark:focus:border-primary-500 sm:text-sm rounded-md disabled:opacity-50 ${className}`}
{...filteredProps}
>
{children}

View File

@@ -2,6 +2,3 @@
import 'intersection-observer';
import 'requestidlecallback';
import objectFitImages from 'object-fit-images';
objectFitImages();

View File

@@ -95,7 +95,7 @@ const AccountGallery = () => {
const media = (attachment.status as Status).media_attachments;
const index = media.findIndex((x) => x.id === attachment.id);
dispatch(openModal('MEDIA', { media, index, status: attachment.status, account: attachment.account }));
dispatch(openModal('MEDIA', { media, index, status: attachment.status }));
}
};

View File

@@ -5,7 +5,7 @@ import { __stub } from 'soapbox/api';
import { ChatContext } from 'soapbox/contexts/chat-context';
import { StatProvider } from 'soapbox/contexts/stat-context';
import chats from 'soapbox/jest/fixtures/chats.json';
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
import { mockStore, render, rootState, screen, waitFor } from 'soapbox/jest/test-helpers';
import ChatPane from '../chat-pane';
@@ -23,7 +23,12 @@ const renderComponentWithChatContext = (store = {}) => render(
describe('<ChatPane />', () => {
describe('when there are no chats', () => {
let store: ReturnType<typeof mockStore>;
beforeEach(() => {
const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.2.0)');
store = mockStore(state);
__stub((mock) => {
mock.onGet('/api/v1/pleroma/chats').reply(200, [], {
link: null,
@@ -32,7 +37,7 @@ describe('<ChatPane />', () => {
});
it('renders the blankslate', async () => {
renderComponentWithChatContext();
renderComponentWithChatContext(store);
await waitFor(() => {
expect(screen.getByTestId('chat-pane-blankslate')).toBeInTheDocument();
@@ -57,4 +62,4 @@ describe('<ChatPane />', () => {
});
});
});
});
});

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { __stub } from 'soapbox/api';
import { render, screen } from '../../../../jest/test-helpers';
import { render, screen, waitFor } from '../../../../jest/test-helpers';
import Search from '../search';
describe('<Search />', () => {
@@ -22,7 +22,9 @@ describe('<Search />', () => {
await user.type(screen.getByLabelText('Search'), '@jus');
expect(screen.getByLabelText('Search')).toHaveValue('@jus');
expect(screen.getByTestId('account')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByLabelText('Search')).toHaveValue('@jus');
expect(screen.getByTestId('account')).toBeInTheDocument();
});
});
});

View File

@@ -29,7 +29,7 @@ const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, de
let itemsDimensions: Record<string, string>[] = [];
if (size === 1) {
style.height = width;
style.height = width! * 9 / 16;
itemsDimensions = [
{ w: '100%', h: '100%' },

View File

@@ -52,7 +52,7 @@ const Settings = () => {
const isMfaEnabled = mfa.getIn(['settings', 'totp']);
useEffect(() => {
dispatch(fetchMfa());
if (features.security) dispatch(fetchMfa());
}, [dispatch]);
if (!account) return null;

View File

@@ -111,7 +111,7 @@ const Card: React.FC<ICard> = ({
// Constrain to a sane limit
// https://en.wikipedia.org/wiki/Aspect_ratio_(image)
return Math.min(Math.max(1, ratio), 4);
return Math.min(Math.max(9 / 16, ratio), 4);
};
const interactive = card.type !== 'link';

View File

@@ -27,7 +27,7 @@ const FloatingActionButton: React.FC<IFloatingActionButton> = () => {
onClick={handleOpenComposeModal}
className={clsx(
'p-4 inline-flex items-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all',
'bg-primary-500 hover:bg-primary-400 dark:hover:bg-primary-600 border-transparent focus:bg-primary-500 text-gray-100 focus:ring-primary-300',
'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300',
)}
aria-label={intl.formatMessage(messages.publish)}
>

View File

@@ -13,7 +13,7 @@ import Video from 'soapbox/features/video';
import ImageLoader from '../image-loader';
import type { List as ImmutableList } from 'immutable';
import type { Account, Attachment, Status } from 'soapbox/types/entities';
import type { Attachment, Status } from 'soapbox/types/entities';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -24,7 +24,6 @@ const messages = defineMessages({
interface IMediaModal {
media: ImmutableList<Attachment>,
status?: Status,
account: Account,
index: number,
time?: number,
onClose: () => void,
@@ -34,7 +33,6 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
const {
media,
status,
account,
onClose,
time = 0,
} = props;
@@ -94,9 +92,9 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
};
const handleStatusClick: React.MouseEventHandler = e => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
if (status && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
history.push(`/@${account.acct}/posts/${status?.id}`);
history.push(`/@${status.getIn(['account', 'acct'])}/posts/${status?.id}`);
onClose();
}
};
@@ -170,7 +168,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
const width = (attachment.meta.getIn(['original', 'width']) || undefined) as number | undefined;
const height = (attachment.meta.getIn(['original', 'height']) || undefined) as number | undefined;
const link = (status && account && (
const link = (status && (
<a href={status.url} onClick={handleStatusClick}>
<FormattedMessage id='lightbox.view_context' defaultMessage='View context' />
</a>

View File

@@ -31,7 +31,7 @@ const ProfileMediaPanel: React.FC<IProfileMediaPanel> = ({ account }) => {
const media = attachment.getIn(['status', 'media_attachments']) as ImmutableList<Attachment>;
const index = media.findIndex(x => x.id === attachment.id);
dispatch(openModal('MEDIA', { media, index, status: attachment.status, account: attachment.account }));
dispatch(openModal('MEDIA', { media, index, status: attachment.status }));
}
};

View File

@@ -28,15 +28,13 @@ function loadPolyfills() {
window.Symbol
);
// Latest version of Firefox and Safari do not have IntersectionObserver.
// Edge does not have requestIdleCallback and object-fit CSS property.
// Older versions of Firefox and Safari do not have IntersectionObserver.
// This avoids shipping them all the polyfills.
const needsExtraPolyfills = !(
window.IntersectionObserver &&
window.IntersectionObserverEntry &&
'isIntersecting' in IntersectionObserverEntry.prototype &&
window.requestIdleCallback &&
'object-fit' in (new Image()).style
window.requestIdleCallback
);
return Promise.all([

File diff suppressed because it is too large Load Diff

View File

@@ -110,12 +110,15 @@
"admin.reports.empty_message": "There are no open reports. If a user gets reported, they will show up here.",
"admin.reports.report_closed_message": "Report on @{name} was closed",
"admin.reports.report_title": "Report on {acct}",
"admin.software.backend": "Backend",
"admin.software.frontend": "Frontend",
"admin.statuses.actions.delete_status": "Delete post",
"admin.statuses.actions.mark_status_not_sensitive": "Mark post not sensitive",
"admin.statuses.actions.mark_status_sensitive": "Mark post sensitive",
"admin.statuses.status_deleted_message": "Post by @{acct} was deleted",
"admin.statuses.status_marked_message_not_sensitive": "Post by @{acct} was marked not sensitive",
"admin.statuses.status_marked_message_sensitive": "Post by @{acct} was marked sensitive",
"admin.theme.title": "Theme",
"admin.user_index.empty": "No users found.",
"admin.user_index.search_input_placeholder": "Who are you looking for?",
"admin.users.actions.deactivate_user": "Deactivate @{name}",
@@ -147,7 +150,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Something went wrong.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "Oops!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@@ -186,19 +188,78 @@
"bundle_modal_error.message": "Something went wrong while loading this modal.",
"bundle_modal_error.retry": "Try again",
"card.back.label": "Back",
"chat_box.actions.send": "Send",
"chat_box.input.placeholder": "Send a message…",
"chat_panels.main_window.empty": "No chats found. To start a chat, visit a user's profile.",
"chat_panels.main_window.title": "Chats",
"chat_window.close": "Close chat",
"chat.actions.send": "Send",
"chat.failed_to_send": "Message failed to send.",
"chat.input.placeholder": "Type a message",
"chat.page_settings.accepting_messages.label": "Allow users to start a new chat with you",
"chat.page_settings.play_sounds.label": "Play a sound when you receive a message",
"chat.page_settings.preferences": "Preferences",
"chat.page_settings.privacy": "Privacy",
"chat.page_settings.submit": "Save",
"chat.page_settings.title": "Message Settings",
"chat.retry": "Retry?",
"chat.welcome.accepting_messages.label": "Allow users to start a new chat with you",
"chat.welcome.notice": "You can change these settings later.",
"chat.welcome.submit": "Save & Continue",
"chat.welcome.subtitle": "Exchange direct messages with other users.",
"chat.welcome.title": "Welcome to {br} Chats!",
"chat_composer.unblock": "Unblock",
"chat_list_item.blocked_you": "This user has blocked you",
"chat_list_item.blocking": "You have blocked this user",
"chat_message_list.blocked": "You blocked this user",
"chat_message_list.blockedBy": "You are blocked by",
"chat_message_list.network_failure.action": "Try again",
"chat_message_list.network_failure.subtitle": "We encountered a network failure.",
"chat_message_list.network_failure.title": "Whoops!",
"chat_message_list_intro.actions.accept": "Accept",
"chat_message_list_intro.actions.leave_chat": "Leave chat",
"chat_message_list_intro.actions.message_lifespan": "Messages older than {day} days are deleted.",
"chat_message_list_intro.actions.report": "Report",
"chat_message_list_intro.intro": "wants to start a chat with you",
"chat_message_list_intro.leave_chat.confirm": "Leave Chat",
"chat_message_list_intro.leave_chat.heading": "Leave Chat",
"chat_message_list_intro.leave_chat.message": "Are you sure you want to leave this chat? Messages will be deleted for you and this chat will be removed from your inbox.",
"chat_search.blankslate.body": "Search for someone to chat with.",
"chat_search.blankslate.title": "Start a chat",
"chat_search.empty_results_blankslate.action": "Message someone",
"chat_search.empty_results_blankslate.body": "Try searching for another name.",
"chat_search.empty_results_blankslate.title": "No matches found",
"chat_search.title": "Messages",
"chat_settings.auto_delete.14days": "14 days",
"chat_settings.auto_delete.2minutes": "2 minutes",
"chat_settings.auto_delete.30days": "30 days",
"chat_settings.auto_delete.7days": "7 days",
"chat_settings.auto_delete.90days": "90 days",
"chat_settings.auto_delete.days": "{day} days",
"chat_settings.auto_delete.hint": "Sent messages will auto-delete after the time period selected",
"chat_settings.auto_delete.label": "Auto-delete messages",
"chat_settings.block.confirm": "Block",
"chat_settings.block.heading": "Block @{acct}",
"chat_settings.block.message": "Blocking will prevent this profile from direct messaging you and viewing your content. You can unblock later.",
"chat_settings.leave.confirm": "Leave Chat",
"chat_settings.leave.heading": "Leave Chat",
"chat_settings.leave.message": "Are you sure you want to leave this chat? Messages will be deleted for you and this chat will be removed from your inbox.",
"chat_settings.options.block_user": "Block @{acct}",
"chat_settings.options.leave_chat": "Leave Chat",
"chat_settings.options.report_user": "Report @{acct}",
"chat_settings.options.unblock_user": "Unblock @{acct}",
"chat_settings.title": "Chat Details",
"chat_settings.unblock.confirm": "Unblock",
"chat_settings.unblock.heading": "Unblock @{acct}",
"chat_settings.unblock.message": "Unblocking will allow this profile to direct message you and view your content.",
"chat_window.auto_delete_label": "Auto-delete after {day} days",
"chat_window.auto_delete_tooltip": "Chat messages are set to auto-delete after {day} days upon sending.",
"chats.actions.copy": "Copy",
"chats.actions.delete": "Delete message",
"chats.actions.deleteForMe": "Delete for me",
"chats.actions.more": "More",
"chats.actions.report": "Report user",
"chats.attachment": "Attachment",
"chats.attachment_image": "Image",
"chats.audio_toggle_off": "Audio notification off",
"chats.audio_toggle_on": "Audio notification on",
"chats.dividers.today": "Today",
"chats.main.blankslate.new_chat": "Message someone",
"chats.main.blankslate.subtitle": "Search for someone to chat with",
"chats.main.blankslate.title": "No messages yet",
"chats.main.blankslate_with_chats.subtitle": "Select from one of your open chats or create a new message.",
"chats.main.blankslate_with_chats.title": "Select a chat",
"chats.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
"column.admin.dashboard": "Dashboard",
@@ -226,6 +287,9 @@
"column.directory": "Browse profiles",
"column.domain_blocks": "Hidden domains",
"column.edit_profile": "Edit profile",
"column.event_map": "Event location",
"column.event_participants": "Event participants",
"column.events": "Events",
"column.export_data": "Export data",
"column.familiar_followers": "People you know following {name}",
"column.favourited_statuses": "Liked posts",
@@ -268,15 +332,14 @@
"column.pins": "Pinned posts",
"column.preferences": "Preferences",
"column.public": "Federated timeline",
"column.quotes": "Post quotes",
"column.reactions": "Reactions",
"column.reblogs": "Reposts",
"column.remote": "Federated timeline",
"column.scheduled_statuses": "Scheduled Posts",
"column.search": "Search",
"column.settings_store": "Settings store",
"column.soapbox_config": "Soapbox config",
"column.test": "Test timeline",
"column_back_button.label": "Back",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@@ -286,7 +349,33 @@
"compose.edit_success": "Your post was edited",
"compose.invalid_schedule": "You must schedule a post at least 5 minutes out.",
"compose.submit_success": "Your post was sent!",
"compose_event.create": "Create",
"compose_event.edit_success": "Your event was edited",
"compose_event.fields.approval_required": "I want to approve participation requests manually",
"compose_event.fields.banner_label": "Event banner",
"compose_event.fields.description_hint": "Markdown syntax is supported",
"compose_event.fields.description_label": "Event description",
"compose_event.fields.description_placeholder": "Description",
"compose_event.fields.end_time_label": "Event end date",
"compose_event.fields.end_time_placeholder": "Event ends on…",
"compose_event.fields.has_end_time": "The event has end date",
"compose_event.fields.location_label": "Event location",
"compose_event.fields.name_label": "Event name",
"compose_event.fields.name_placeholder": "Name",
"compose_event.fields.start_time_label": "Event start date",
"compose_event.fields.start_time_placeholder": "Event begins on…",
"compose_event.participation_requests.authorize": "Authorize",
"compose_event.participation_requests.authorize_success": "User accepted",
"compose_event.participation_requests.reject": "Reject",
"compose_event.participation_requests.reject_success": "User rejected",
"compose_event.reset_location": "Reset location",
"compose_event.submit_success": "Your event was created",
"compose_event.tabs.edit": "Edit details",
"compose_event.tabs.pending": "Manage requests",
"compose_event.update": "Update",
"compose_event.upload_banner": "Upload event banner",
"compose_form.direct_message_warning": "This post will only be sent to the mentioned users.",
"compose_form.event_placeholder": "Post to this event",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
@@ -342,15 +431,22 @@
"confirmations.cancel_editing.confirm": "Cancel editing",
"confirmations.cancel_editing.heading": "Cancel post editing",
"confirmations.cancel_editing.message": "Are you sure you want to cancel editing this post? All changes will be lost.",
"confirmations.cancel_event_editing.heading": "Cancel event editing",
"confirmations.cancel_event_editing.message": "Are you sure you want to cancel editing this event? All changes will be lost.",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.heading": "Delete post",
"confirmations.delete.message": "Are you sure you want to delete this post?",
"confirmations.delete_event.confirm": "Delete",
"confirmations.delete_event.heading": "Delete event",
"confirmations.delete_event.message": "Are you sure you want to delete this event?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.heading": "Delete list",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.heading": "Block {domain}",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications.",
"confirmations.leave_event.confirm": "Leave event",
"confirmations.leave_event.message": "If you want to rejoin the event, the request will be manually reviewed again. Are you sure you want to proceed?",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.heading": "Mute @{name}",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
@@ -400,6 +496,7 @@
"developers.navigation.network_error_label": "Network error",
"developers.navigation.service_worker_label": "Service Worker",
"developers.navigation.settings_store_label": "Settings store",
"developers.navigation.show_toast": "Trigger Toast",
"developers.navigation.test_timeline_label": "Test timeline",
"developers.settings_store.advanced": "Advanced settings",
"developers.settings_store.hint": "It is possible to directly edit your user settings here. BE CAREFUL! Editing this section can break your account, and you will only be able to recover through the API.",
@@ -498,6 +595,8 @@
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no hidden domains yet.",
"empty_column.event_participant_requests": "There are no pending event participation requests.",
"empty_column.event_participants": "No one joined this event yet. When someone does, they will show up here.",
"empty_column.favourited_statuses": "You don't have any liked posts yet. When you like one, it will show up here.",
"empty_column.favourites": "No one has liked this post yet. When someone does, they will show up here.",
"empty_column.filters": "You haven't created any muted words yet.",
@@ -514,12 +613,36 @@
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.notifications_filtered": "You don't have any notifications of this type yet.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
"empty_column.quotes": "This post has not been quoted yet.",
"empty_column.remote": "There is nothing here! Manually follow users from {instance} to fill it up.",
"empty_column.scheduled_statuses": "You don't have any scheduled statuses yet. When you add one, it will show up here.",
"empty_column.search.accounts": "There are no people results for \"{term}\"",
"empty_column.search.hashtags": "There are no hashtags results for \"{term}\"",
"empty_column.search.statuses": "There are no posts results for \"{term}\"",
"empty_column.test": "The test timeline is empty.",
"event.banner": "Event banner",
"event.copy": "Copy link to event",
"event.date": "Date",
"event.description": "Description",
"event.discussion.empty": "No one has commented this event yet. When someone does, they will appear here.",
"event.export_ics": "Export to your calendar",
"event.external": "View event on {domain}",
"event.join_state.accept": "Going",
"event.join_state.empty": "Participate",
"event.join_state.pending": "Pending",
"event.join_state.rejected": "Going",
"event.location": "Location",
"event.manage": "Manage",
"event.organized_by": "Organized by {name}",
"event.participants": "{count} {rawCount, plural, one {person} other {people}} going",
"event.show_on_map": "Show on map",
"event.website": "External links",
"event_map.navigate": "Navigate",
"events.create_event": "Create event",
"events.joined_events": "Joined events",
"events.joined_events.empty": "You haven't joined any event yet.",
"events.recent_events": "Recent events",
"events.recent_events.empty": "There are no public events yet.",
"export_data.actions.export": "Export",
"export_data.actions.export_blocks": "Export blocks",
"export_data.actions.export_follows": "Export follows",
@@ -600,6 +723,12 @@
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
"join_event.hint": "You can tell the organizer why do you want to participate in this event:",
"join_event.join": "Request join",
"join_event.placeholder": "Message to organizer",
"join_event.request_success": "Requested to join the event",
"join_event.success": "Joined the event",
"join_event.title": "Join event",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.blocked": "to open blocked users list",
"keyboard_shortcuts.boost": "to repost",
@@ -648,6 +777,7 @@
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"loading_indicator.label": "Loading…",
"location_search.placeholder": "Find an address",
"login.fields.instance_label": "Instance",
"login.fields.instance_placeholder": "example.com",
"login.fields.otp_code_hint": "Enter the two-factor code generated by your phone app or use one of your recovery codes",
@@ -696,6 +826,8 @@
"missing_description_modal.text": "You have not entered a description for all attachments. Continue anyway?",
"missing_indicator.label": "Not found",
"missing_indicator.sublabel": "This resource could not be found",
"modals.policy.submit": "Accept & Continue",
"modals.policy.updateTitle": "Youve scored the latest version of {siteTitle}! Take a moment to review the exciting new things weve been working on.",
"moderation_overlay.contact": "Contact",
"moderation_overlay.hide": "Hide content",
"moderation_overlay.show": "Show Content",
@@ -722,8 +854,10 @@
"navigation_bar.compose": "Compose a post",
"navigation_bar.compose_direct": "Direct message",
"navigation_bar.compose_edit": "Edit post",
"navigation_bar.compose_event": "Manage event",
"navigation_bar.compose_quote": "Quote post",
"navigation_bar.compose_reply": "Reply to post",
"navigation_bar.create_event": "Create new event",
"navigation_bar.domain_blocks": "Domain blocks",
"navigation_bar.favourites": "Likes",
"navigation_bar.filters": "Filters",
@@ -746,6 +880,9 @@
"notification.others": " + {count} {count, plural, one {other} other {others}}",
"notification.pleroma:chat_mention": "{name} sent you a message",
"notification.pleroma:emoji_reaction": "{name} reacted to your post",
"notification.pleroma:event_reminder": "An event you are participating in starts soon",
"notification.pleroma:participation_accepted": "You were accepted to join the event",
"notification.pleroma:participation_request": "{name} wants to join your event",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} reposted your post",
"notification.status": "{name} just posted",
@@ -819,6 +956,8 @@
"preferences.fields.content_type_label": "Default post format",
"preferences.fields.delete_modal_label": "Show confirmation dialog before deleting a post",
"preferences.fields.demetricator_label": "Use Demetricator",
"preferences.fields.demo_hint": "Use the default Soapbox logo and color scheme. Useful for taking screenshots.",
"preferences.fields.demo_label": "Demo mode",
"preferences.fields.display_media.default": "Hide posts marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide posts",
"preferences.fields.display_media.show_all": "Always show posts",
@@ -834,7 +973,6 @@
"preferences.fields.underline_links_label": "Always underline links in posts",
"preferences.fields.unfollow_modal_label": "Show confirmation dialog before unfollowing someone",
"preferences.hints.demetricator": "Decrease social media anxiety by hiding all numbers from the site.",
"preferences.hints.feed": "In your home feed",
"preferences.notifications.advanced": "Show all notification categories",
"preferences.options.content_type_markdown": "Markdown",
"preferences.options.content_type_plaintext": "Plain text",
@@ -907,6 +1045,8 @@
"remote_instance.unpin_host": "Unpin {host}",
"remote_interaction.account_placeholder": "Enter your username@domain you want to act from",
"remote_interaction.divider": "or",
"remote_interaction.event_join": "Proceed to join",
"remote_interaction.event_join_title": "Join an event remotely",
"remote_interaction.favourite": "Proceed to like",
"remote_interaction.favourite_title": "Like a post remotely",
"remote_interaction.follow": "Proceed to follow",
@@ -928,6 +1068,8 @@
"reply_mentions.reply_empty": "Replying to post",
"report.block": "Block {target}",
"report.block_hint": "Do you also want to block this account?",
"report.chatMessage.context": "When reporting a users message, the five messages before and five messages after the one selected will be passed along to our moderation team for context.",
"report.chatMessage.title": "Report message",
"report.confirmation.content": "If we find that this account is violating the {link} we will take further action on the matter.",
"report.confirmation.title": "Thanks for submitting your report.",
"report.done": "Done",
@@ -989,6 +1131,7 @@
"settings.configure_mfa": "Configure MFA",
"settings.delete_account": "Delete Account",
"settings.edit_profile": "Edit Profile",
"settings.messages.label": "Allow users to start a new chat with you",
"settings.other": "Other options",
"settings.preferences": "Preferences",
"settings.profile": "Profile",
@@ -1016,7 +1159,6 @@
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
"sms_verification.sent.header": "Verification code",
"sms_verification.success": "A verification code has been sent to your phone number.",
"toast.view": "View",
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
@@ -1029,9 +1171,8 @@
"soapbox_config.display_fqn_label": "Display domain (eg @user@domain) for local accounts.",
"soapbox_config.feed_injection_hint": "Inject the feed with additional content, such as suggested profiles.",
"soapbox_config.feed_injection_label": "Feed injection",
"soapbox_config.fields.accent_color_label": "Accent color",
"soapbox_config.fields.brand_color_label": "Brand color",
"soapbox_config.fields.crypto_addresses_label": "Cryptocurrency addresses",
"soapbox_config.fields.edit_theme_label": "Edit theme",
"soapbox_config.fields.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
"soapbox_config.fields.promo_panel_fields_label": "Promo panel items",
@@ -1039,6 +1180,7 @@
"soapbox_config.greentext_label": "Enable greentext support",
"soapbox_config.headings.advanced": "Advanced",
"soapbox_config.headings.cryptocurrency": "Cryptocurrency",
"soapbox_config.headings.events": "Events",
"soapbox_config.headings.navigation": "Navigation",
"soapbox_config.headings.options": "Options",
"soapbox_config.headings.theme": "Theme",
@@ -1060,6 +1202,8 @@
"soapbox_config.single_user_mode_label": "Single user mode",
"soapbox_config.single_user_mode_profile_hint": "@handle",
"soapbox_config.single_user_mode_profile_label": "Main user handle",
"soapbox_config.tile_server_attribution_label": "Map tiles attribution",
"soapbox_config.tile_server_label": "Map tile server",
"soapbox_config.verified_can_edit_name_label": "Allow verified users to edit their own display name.",
"sponsored.info.message": "{siteTitle} displays ads to help fund our service.",
"sponsored.info.title": "Why am I seeing this ad?",
@@ -1081,6 +1225,7 @@
"status.favourite": "Like",
"status.filtered": "Filtered",
"status.interactions.favourites": "{count, plural, one {Like} other {Likes}}",
"status.interactions.quotes": "{count, plural, one {Quote} other {Quotes}}",
"status.interactions.reblogs": "{count, plural, one {Repost} other {Reposts}}",
"status.load_more": "Load more",
"status.mention": "Mention @{name}",
@@ -1139,7 +1284,6 @@
"sw.update_text": "An update is available.",
"sw.url": "Script URL",
"tabs_bar.all": "All",
"tabs_bar.chats": "Chats",
"tabs_bar.dashboard": "Dashboard",
"tabs_bar.fediverse": "Fediverse",
"tabs_bar.home": "Home",
@@ -1149,6 +1293,13 @@
"tabs_bar.profile": "Profile",
"tabs_bar.search": "Search",
"tabs_bar.settings": "Settings",
"theme_editor.Reset": "Reset",
"theme_editor.export": "Export theme",
"theme_editor.import": "Import theme",
"theme_editor.import_success": "Theme was successfully imported!",
"theme_editor.restore": "Restore default theme",
"theme_editor.save": "Save theme",
"theme_editor.saved": "Theme updated!",
"theme_toggle.dark": "Dark",
"theme_toggle.light": "Light",
"theme_toggle.system": "System",
@@ -1161,10 +1312,10 @@
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
"time_remaining.moments": "Moments remaining",
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
"toast.view": "View",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
"trends.title": "Trends",
"trendsPanel.viewAll": "View all",
"ui.beforeunload": "Your draft will be lost if you leave.",
"unauthorized_modal.text": "You need to be logged in to do that.",
"unauthorized_modal.title": "Sign up for {site_title}",
"upload_area.title": "Drag & drop to upload",

View File

@@ -112,6 +112,17 @@ const fixAkkoma = (instance: ImmutableMap<string, any>) => {
}
};
/** Set Takahe version to a Pleroma-like string */
const fixTakahe = (instance: ImmutableMap<string, any>) => {
const version: string = instance.get('version', '');
if (version.startsWith('takahe/')) {
return instance.set('version', `0.0.0 (compatible; Takahe ${version.slice(7)})`);
} else {
return instance;
}
};
// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format
export const normalizeInstance = (instance: Record<string, any>) => {
return InstanceRecord(
@@ -131,6 +142,7 @@ export const normalizeInstance = (instance: Record<string, any>) => {
// Normalize version
normalizeVersion(instance);
fixTakahe(instance);
fixAkkoma(instance);
// Merge defaults

View File

@@ -178,7 +178,8 @@ describe('useChats', () => {
describe('with a successful request', () => {
beforeEach(() => {
store = mockStore(rootState);
const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.2.0)');
store = mockStore(state);
__stub((mock) => {
mock.onGet('/api/v1/pleroma/chats')
@@ -378,4 +379,4 @@ describe('useChatActions', () => {
expect((nextQueryData as any).message_expiration).toBe(1200);
});
});
});
});

View File

@@ -157,6 +157,7 @@ const useChats = (search?: string) => {
const queryInfo = useInfiniteQuery(ChatKeys.chatSearch(search), ({ pageParam }) => getChats(pageParam), {
keepPreviousData: true,
enabled: features.chats,
getNextPageParam: (config) => {
if (config.hasMore) {
return { link: config.link };

View File

@@ -64,6 +64,12 @@ export const GLITCH = 'glitch';
*/
export const AKKOMA = 'akkoma';
/**
* Takahē, backend with support for serving multiple domains.
* @see {@link https://jointakahe.org/}
*/
export const TAKAHE = 'Takahe';
/** Parse features for the given instance */
const getInstanceFeatures = (instance: Instance) => {
const v = parseVersion(instance.version);
@@ -288,6 +294,7 @@ const getInstanceFeatures = (instance: Instance) => {
v.software === MASTODON && gte(v.compatVersion, '2.6.0'),
v.software === PLEROMA && gte(v.version, '0.9.9'),
v.software === PIXELFED,
v.software === TAKAHE,
]),
/**
@@ -299,6 +306,14 @@ const getInstanceFeatures = (instance: Instance) => {
v.software === PLEROMA && gte(v.version, '0.9.9'),
]),
editProfile: any([
v.software === MASTODON,
v.software === MITRA,
v.software === PIXELFED,
v.software === PLEROMA,
v.software === TRUTHSOCIAL,
]),
editStatuses: any([
v.software === MASTODON && gte(v.version, '3.5.0'),
features.includes('editing'),
@@ -574,6 +589,7 @@ const getInstanceFeatures = (instance: Instance) => {
publicTimeline: any([
v.software === MASTODON,
v.software === PLEROMA,
v.software === TAKAHE,
]),
/**

View File

@@ -1,4 +1,4 @@
export const minimumAspectRatio = 1; // Square
export const minimumAspectRatio = 9 / 16; // Portrait phone
export const maximumAspectRatio = 10; // Generous min-height
export const isPanoramic = (ar: number) => {