Merge remote-tracking branch 'origin/develop' into accounts-scss

environments/review-accounts-s-gnzsor/deployments/2278
Alex Gleason 2 years ago
commit d3f93d0325
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7

@ -9,7 +9,7 @@
"declaration-block-no-redundant-longhand-properties": null,
"declaration-colon-newline-after": null,
"declaration-empty-line-before": "never",
"font-family-no-missing-generic-family-keyword": [true, { "ignoreFontFamilies": ["ForkAwesome", "Font Awesome 5 Free", "OpenDyslexic", "soapbox"] }],
"font-family-no-missing-generic-family-keyword": [true, { "ignoreFontFamilies": ["ForkAwesome", "Font Awesome 5 Free"] }],
"max-line-length": null,
"no-descending-specificity": null,
"no-duplicate-selectors": null,

@ -1 +1 @@
nodejs 18.12.1
nodejs 18.13.0

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Posts: letterbox images to 19:6 again.
- Status Info: moved context (repost, pinned) to improve UX.
- Posts: remove file icon from empty link previews.
### Fixed
- Layout: use accent color for "floating action button" (mobile compose button).
@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Modals: close modal when navigating to a different page.
- Modals: fix "View context" button in media modal.
- Posts: let unauthenticated users to translate posts if allowed by backend.
- Chats: fix jumpy scrollbar.
## [3.0.0] - 2022-12-25

@ -1,17 +0,0 @@
import loadPolyfills from './soapbox/load-polyfills';
// Load iframe event listener
require('./soapbox/iframe');
// @ts-ignore
require.context('./assets/images/', true);
// Load stylesheet
require('react-datepicker/dist/react-datepicker.css');
require('./styles/application.scss');
loadPolyfills().then(() => {
require('./soapbox/main').default();
}).catch(e => {
console.error(e);
});

@ -1,94 +0,0 @@
Copyright (c) 2019-07-29, Abbie Gonzalez (https://abbiecod.es|support@abbiecod.es),
with Reserved Font Name OpenDyslexic.
Copyright (c) 12/2012 - 2019
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

@ -10,10 +10,10 @@ import {
} from './importer';
import type { AxiosError, CancelToken } from 'axios';
import type { History } from 'history';
import type { Map as ImmutableMap } from 'immutable';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Status } from 'soapbox/types/entities';
import type { History } from 'soapbox/types/history';
const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST';
const ACCOUNT_CREATE_SUCCESS = 'ACCOUNT_CREATE_SUCCESS';

@ -6,8 +6,8 @@ import { getFeatures } from 'soapbox/utils/features';
import api, { getLinks } from '../api';
import type { History } from 'history';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { History } from 'soapbox/types/history';
const CHATS_FETCH_REQUEST = 'CHATS_FETCH_REQUEST';
const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS';

@ -19,11 +19,11 @@ import { openModal, closeModal } from './modals';
import { getSettings } from './settings';
import { createStatus } from './statuses';
import type { History } from 'history';
import type { Emoji } from 'soapbox/components/autosuggest-emoji';
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { Account, APIEntity, Status, Tag } from 'soapbox/types/entities';
import type { History } from 'soapbox/types/history';
const { CancelToken, isCancel } = axios;

@ -107,7 +107,10 @@ const updateNotificationsQueue = (notification: APIEntity, intlMessages: Record<
// Desktop notifications
try {
if (showAlert && !filtered) {
// eslint-disable-next-line compat/compat
const isNotificationsEnabled = window.Notification?.permission === 'granted';
if (showAlert && !filtered && isNotificationsEnabled) {
const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : '');

@ -47,7 +47,6 @@ const defaultSettings = ImmutableMap({
autoloadMore: true,
systemFont: false,
dyslexicFont: false,
demetricator: false,
isDeveloper: false,

@ -1,50 +0,0 @@
'use strict';
import 'intl';
import 'intl/locale-data/jsonp/en';
import 'es6-symbol/implement';
// @ts-ignore: No types
import includes from 'array-includes';
// @ts-ignore: No types
import isNaN from 'is-nan';
import assign from 'object-assign';
// @ts-ignore: No types
import values from 'object.values';
import { decode as decodeBase64 } from './utils/base64';
if (!Array.prototype.includes) {
includes.shim();
}
if (!Object.assign) {
Object.assign = assign;
}
if (!Object.values) {
values.shim();
}
if (!Number.isNaN) {
Number.isNaN = isNaN;
}
if (!HTMLCanvasElement.prototype.toBlob) {
const BASE64_MARKER = ';base64,';
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value(callback: any, type = 'image/png', quality: any) {
const dataURL = this.toDataURL(type, quality);
let data;
if (dataURL.includes(BASE64_MARKER)) {
const [, base64] = dataURL.split(BASE64_MARKER);
data = decodeBase64(base64);
} else {
[, data] = dataURL.split(',');
}
callback(new Blob([data], { type }));
},
});
}

@ -1,8 +1,8 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { unblockDomain } from 'soapbox/actions/domain-blocks';
import { useAppDispatch } from 'soapbox/hooks';
import { HStack, IconButton, Text } from './ui';
@ -16,7 +16,7 @@ interface IDomain {
}
const Domain: React.FC<IDomain> = ({ domain }) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
// const onBlockDomain = () => {

@ -1,12 +1,11 @@
import classNames from 'clsx';
import React, { useState, useEffect, useRef } from 'react';
import { usePopper } from 'react-popper';
import { useDispatch } from 'react-redux';
import { simpleEmojiReact } from 'soapbox/actions/emoji-reacts';
import { openModal } from 'soapbox/actions/modals';
import { EmojiSelector } from 'soapbox/components/ui';
import { useAppSelector, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks';
import { isUserTouching } from 'soapbox/is-mobile';
import { getReactForStatus } from 'soapbox/utils/emoji-reacts';
@ -17,7 +16,7 @@ interface IEmojiButtonWrapper {
/** Provides emoji reaction functionality to the underlying button component */
const EmojiButtonWrapper: React.FC<IEmojiButtonWrapper> = ({ statusId, children }): JSX.Element | null => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const ownAccount = useOwnAccount();
const status = useAppSelector(state => state.statuses.get(statusId));
const soapboxConfig = useSoapboxConfig();

@ -90,7 +90,7 @@ const IconButton: React.FC<IIconButton> = ({
type='button'
>
<div>
<Icon className={iconClassName} src={src} fixedWidth aria-hidden='true' />
<Icon className={iconClassName} src={src} aria-hidden='true' />
</div>
{text && <span className='icon-button__text'>{text}</span>}
</button>

@ -1,27 +1,28 @@
/**
* Icon: abstract icon class that can render icons from multiple sets.
* Icon: abstact component to render SVG icons.
* @module soapbox/components/icon
* @see soapbox/components/fork_awesome_icon
* @see soapbox/components/svg_icon
*/
import classNames from 'clsx';
import React from 'react';
import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
import ForkAwesomeIcon, { IForkAwesomeIcon } from './fork-awesome-icon';
import SvgIcon, { ISvgIcon } from './svg-icon';
export type IIcon = IForkAwesomeIcon | ISvgIcon;
const Icon: React.FC<IIcon> = (props) => {
if ((props as ISvgIcon).src) {
const { src, ...rest } = (props as ISvgIcon);
return <SvgIcon src={src} {...rest} />;
} else {
const { id, fixedWidth, ...rest } = (props as IForkAwesomeIcon);
return <ForkAwesomeIcon id={id} fixedWidth={fixedWidth} {...rest} />;
export interface IIcon extends React.HTMLAttributes<HTMLDivElement> {
src: string,
id?: string,
alt?: string,
className?: string,
}
const Icon: React.FC<IIcon> = ({ src, alt, className, ...rest }) => {
return (
<div
className={classNames('svg-icon', className)}
{...rest}
>
<InlineSVG src={src} title={alt} loader={<></>} />
</div>
);
};
export default Icon;

@ -1,5 +1,5 @@
import classNames from 'clsx';
import React, { useState, useRef, useEffect } from 'react';
import React, { useState, useRef, useLayoutEffect } from 'react';
import Blurhash from 'soapbox/components/blurhash';
import Icon from 'soapbox/components/icon';
@ -533,7 +533,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
/>
));
useEffect(() => {
useLayoutEffect(() => {
if (node.current) {
const { offsetWidth } = node.current;

@ -11,7 +11,6 @@ import { useAppDispatch, usePrevious } from 'soapbox/hooks';
import { queryClient } from 'soapbox/queries/client';
import { IPolicy, PolicyKeys } from 'soapbox/queries/policies';
import type { UnregisterCallback } from 'history';
import type { ModalType } from 'soapbox/features/ui/components/modal-root';
import type { ReducerCompose } from 'soapbox/reducers/compose';
import type { ReducerRecord as ReducerComposeEvent } from 'soapbox/reducers/compose-event';
@ -55,7 +54,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
const ref = useRef<HTMLDivElement>(null);
const activeElement = useRef<HTMLDivElement | null>(revealed ? document.activeElement as HTMLDivElement | null : null);
const modalHistoryKey = useRef<number>();
const unlistenHistory = useRef<UnregisterCallback>();
const unlistenHistory = useRef<ReturnType<typeof history.listen>>();
const prevChildren = usePrevious(children);
const prevType = usePrevious(type);

@ -24,13 +24,13 @@ type SavedScrollPosition = {
// NOTE: It's crucial to space lists with **padding** instead of margin!
// Pass an `itemClassName` like `pb-3`, NOT a `space-y-3` className
// https://virtuoso.dev/troubleshooting#list-does-not-scroll-to-the-bottom--items-jump-around
const Item: Components<Context>['Item'] = ({ context, ...rest }) => (
const Item: Components<JSX.Element, Context>['Item'] = ({ context, ...rest }) => (
<div className={context?.itemClassName} {...rest} />
);
/** Custom Virtuoso List component for the outer container. */
// Ensure the className winds up here
const List: Components<Context>['List'] = React.forwardRef((props, ref) => {
const List: Components<JSX.Element, Context>['List'] = React.forwardRef((props, ref) => {
const { context, ...rest } = props;
return <div ref={ref} className={context?.listClassName} {...rest} />;
});

@ -2,7 +2,6 @@
import classNames from 'clsx';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link, NavLink } from 'react-router-dom';
import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth';
@ -11,7 +10,7 @@ import { closeSidebar } from 'soapbox/actions/sidebar';
import Account from 'soapbox/components/account';
import { Stack } from 'soapbox/components/ui';
import ProfileStats from 'soapbox/features/ui/components/profile-stats';
import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { makeGetAccount, makeGetOtherAccounts } from 'soapbox/selectors';
import { Divider, HStack, Icon, IconButton, Text } from './ui';
@ -81,7 +80,7 @@ const getOtherAccounts = makeGetOtherAccounts();
const SidebarMenu: React.FC = (): JSX.Element | null => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const features = useFeatures();
const getAccount = makeGetAccount();

@ -26,7 +26,7 @@ interface IReadMoreButton {
const ReadMoreButton: React.FC<IReadMoreButton> = ({ onClick }) => (
<button className='flex items-center text-gray-900 dark:text-gray-300 border-0 bg-transparent p-0 pt-2 hover:underline active:underline' onClick={onClick}>
<FormattedMessage id='status.read_more' defaultMessage='Read more' />
<Icon className='inline-block h-5 w-5' src={require('@tabler/icons/chevron-right.svg')} fixedWidth />
<Icon className='inline-block h-5 w-5' src={require('@tabler/icons/chevron-right.svg')} />
</button>
);

@ -1,29 +0,0 @@
/**
* SvgIcon: abstact component to render SVG icons.
* @module soapbox/components/svg_icon
* @see soapbox/components/icon
*/
import classNames from 'clsx';
import React from 'react';
import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
export interface ISvgIcon extends React.HTMLAttributes<HTMLDivElement> {
src: string,
id?: string,
alt?: string,
className?: string,
}
const SvgIcon: React.FC<ISvgIcon> = ({ src, alt, className, ...rest }) => {
return (
<div
className={classNames('svg-icon', className)}
{...rest}
>
<InlineSVG src={src} title={alt} loader={<></>} />
</div>
);
};
export default SvgIcon;

@ -27,7 +27,7 @@ const FormGroup: React.FC<IFormGroup> = (props) => {
if (React.isValidElement(inputChildren[0])) {
firstChild = React.cloneElement(
inputChildren[0],
{ id: formFieldId, hasError },
{ id: formFieldId },
);
}
const isCheckboxFormGroup = firstChild?.type === Checkbox;

@ -33,8 +33,6 @@ interface IInput extends Pick<React.InputHTMLAttributes<HTMLInputElement>, 'maxL
value?: string | number,
/** Change event handler for the input. */
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void,
/** Whether to display the input in red. */
hasError?: boolean,
/** An element to display as prefix to input. Cannot be used with icon. */
prepend?: React.ReactElement,
/** An element to display as suffix to input. Cannot be used with password type. */
@ -48,7 +46,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
(props, ref) => {
const intl = useIntl();
const { type = 'text', icon, className, outerClassName, hasError, append, prepend, theme = 'normal', ...filteredProps } = props;
const { type = 'text', icon, className, outerClassName, append, prepend, theme = 'normal', ...filteredProps } = props;
const [revealed, setRevealed] = React.useState(false);
@ -91,7 +89,6 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
'rounded-md bg-white dark:bg-gray-900 border-gray-400 dark:border-gray-800': theme === 'normal',
'rounded-full bg-gray-200 border-gray-200 dark:bg-gray-800 dark:border-gray-800 focus:bg-white': theme === 'search',
'pr-7 rtl:pl-7 rtl:pr-3': isPassword || append,
'text-red-600 border-red-600': hasError,
'pl-8': typeof icon !== 'undefined',
'pl-16': typeof prepend !== 'undefined',
}, className)}

@ -271,7 +271,6 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
const bodyClass = classNames('bg-white dark:bg-gray-800 text-base h-full', {
'no-reduce-motion': !settings.get('reduceMotion'),
'underline-links': settings.get('underlineLinks'),
'dyslexic': settings.get('dyslexicFont'),
'demetricator': settings.get('demetricator'),
});

@ -1,9 +1,8 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory, useParams } from 'react-router-dom';
import { toggleMainWindow } from 'soapbox/actions/chats';
import { useOwnAccount, useSettings } from 'soapbox/hooks';
import { useAppDispatch, useOwnAccount, useSettings } from 'soapbox/hooks';
import { IChat, useChat } from 'soapbox/queries/chats';
type WindowState = 'open' | 'minimized';
@ -22,7 +21,7 @@ enum ChatWidgetScreens {
const ChatProvider: React.FC = ({ children }) => {
const history = useHistory();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const settings = useSettings();
const account = useOwnAccount();

@ -1,4 +0,0 @@
'use strict';
import 'intersection-observer';
import 'requestidlecallback';

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useParams } from 'react-router-dom';
@ -66,10 +66,7 @@ const AccountGallery = () => {
const hasMore = useAppSelector((state) => state.timelines.get(`account:${accountId}:media`)?.hasMore);
const [width, setWidth] = useState(323);
const handleRef = (c: HTMLDivElement) => {
if (c) setWidth(c.offsetWidth);
};
const node = useRef<HTMLDivElement>(null);
const handleScrollToBottom = () => {
if (hasMore) {
@ -99,6 +96,12 @@ const AccountGallery = () => {
}
};
useLayoutEffect(() => {
if (node.current) {
setWidth(node.current.offsetWidth);
}
}, [node.current]);
useEffect(() => {
if (accountId && accountId !== -1) {
dispatch(fetchAccount(accountId));
@ -140,7 +143,7 @@ const AccountGallery = () => {
return (
<Column label={`@${accountUsername}`} transparent withHeader={false}>
<div role='feed' className='flex flex-wrap gap-2' ref={handleRef}>
<div role='feed' className='flex flex-wrap gap-2' ref={node}>
{attachments.map((attachment, index) => attachment === null ? (
<LoadMoreMedia key={'more:' + attachments.get(index + 1)?.id} maxId={index > 0 ? (attachments.get(index - 1)?.id || null) : null} onLoadMore={handleLoadMore} />
) : (

@ -1,12 +1,11 @@
import classNames from 'clsx';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { fetchAliasesSuggestions, clearAliasesSuggestions, changeAliasesSuggestions } from 'soapbox/actions/aliases';
import Icon from 'soapbox/components/icon';
import { Button } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
const messages = defineMessages({
search: { id: 'aliases.search', defaultMessage: 'Search your old account' },
@ -14,7 +13,7 @@ const messages = defineMessages({
});
const Search: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const value = useAppSelector(state => state.aliases.suggestions.value);

@ -1,7 +1,7 @@
import classNames from 'clsx';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import Icon from 'soapbox/components/icon';
@ -397,7 +397,7 @@ const Audio: React.FC<IAudio> = (props) => {
const progress = Math.min((currentTime / getDuration()) * 100, 100);
useEffect(() => {
useLayoutEffect(() => {
if (player.current) {
_setDimensions();
}

@ -238,7 +238,6 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
pattern='^[a-zA-Z\d_-]+'
onChange={onUsernameChange}
value={params.get('username', '')}
hasError={usernameUnavailable}
required
/>
</FormGroup>

@ -1,13 +1,12 @@
import debounce from 'lodash/debounce';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { fetchBlocks, expandBlocks } from 'soapbox/actions/blocks';
import ScrollableList from 'soapbox/components/scrollable-list';
import { Column, Spinner } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
const messages = defineMessages({
heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
@ -18,7 +17,7 @@ const handleLoadMore = debounce((dispatch) => {
}, 300, { leading: true });
const Blocks: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const accountIds = useAppSelector((state) => state.user_lists.blocks.items);

@ -66,6 +66,21 @@ const List: Components['List'] = React.forwardRef((props, ref) => {
return <div ref={ref} {...rest} className='mb-2' />;
});
const Scroller: Components['Scroller'] = React.forwardRef((props, ref) => {
const { style, context, ...rest } = props;
return (
<div
{...rest}
ref={ref}
style={{
...style,
scrollbarGutter: 'stable',
}}
/>
);
});
interface IChatMessageList {
/** Chat the messages are being rendered from. */
chat: IChat,
@ -472,6 +487,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
}}
components={{
List,
Scroller,
Header: () => {
if (hasNextPage || isFetchingNextPage) {
return <Spinner withText={false} />;

@ -6,7 +6,7 @@ import { __stub } from 'soapbox/api';
import { normalizeAccount } from 'soapbox/normalizers';
import { ReducerAccount } from 'soapbox/reducers/accounts';
import { render, screen } from '../../../../../jest/test-helpers';
import { render, screen, waitFor } from '../../../../../jest/test-helpers';
import ChatPage from '../chat-page';
describe('<ChatPage />', () => {
@ -48,9 +48,12 @@ describe('<ChatPage />', () => {
await userEvent.click(screen.getByTestId('button'));
expect(screen.getByTestId('chat-page')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent('Chat Settings updated successfully');
});
});
});
describe('when the API returns an error', () => {
beforeEach(() => {
@ -77,8 +80,11 @@ describe('<ChatPage />', () => {
it('renders the Chats', async () => {
render(<ChatPage />, undefined, store);
await userEvent.click(screen.getByTestId('button'));
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent('Chat Settings failed to update.');
});
});
});
});
});

@ -1,5 +1,5 @@
import classNames from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { matchPath, Route, Switch, useHistory } from 'react-router-dom';
import { Stack } from 'soapbox/components/ui';
@ -44,7 +44,7 @@ const ChatPage: React.FC<IChatPage> = ({ chatId }) => {
setHeight(fullHeight - top + offset);
};
useEffect(() => {
useLayoutEffect(() => {
calculateHeight();
}, [containerRef.current]);

@ -238,7 +238,7 @@ const ChatPageMain = () => {
<div className='h-full overflow-hidden'>
<Chat
className='h-full overflow-hidden'
className='h-full'
chat={chat}
inputRef={inputRef}
/>

@ -1,9 +1,7 @@
import classNames from 'clsx';
import { Map as ImmutableMap } from 'immutable';
import debounce from 'lodash/debounce';
import React, { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import {
@ -17,7 +15,8 @@ import {
import AutosuggestAccountInput from 'soapbox/components/autosuggest-account-input';
import { Input } from 'soapbox/components/ui';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { AppDispatch, RootState } from 'soapbox/store';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@ -25,7 +24,7 @@ const messages = defineMessages({
});
function redirectToAccount(accountId: string, routerHistory: any) {
return (_dispatch: any, getState: () => ImmutableMap<string, any>) => {
return (_dispatch: AppDispatch, getState: () => RootState) => {
const acct = getState().getIn(['accounts', accountId, 'acct']);
if (acct && routerHistory) {
@ -49,7 +48,7 @@ const Search = (props: ISearch) => {
openInRoute = false,
} = props;
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const history = useHistory();
const intl = useIntl();

@ -1,9 +1,9 @@
import React, { useState } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { changeSettingImmediate } from 'soapbox/actions/settings';
import { Column, Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui';
import { useAppDispatch } from 'soapbox/hooks';
import toast from 'soapbox/toast';
const messages = defineMessages({
@ -15,7 +15,7 @@ const messages = defineMessages({
});
const DevelopersChallenge = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const [answer, setAnswer] = useState('');

@ -1,11 +1,11 @@
import React from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { changeSettingImmediate } from 'soapbox/actions/settings';
import { Column, Text } from 'soapbox/components/ui';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import { useAppDispatch } from 'soapbox/hooks';
import toast from 'soapbox/toast';
import sourceCode from 'soapbox/utils/code';
@ -31,7 +31,7 @@ const DashWidget: React.FC<IDashWidget> = ({ to, onClick, children }) => {
};
const Developers: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const history = useHistory();
const intl = useIntl();

@ -134,12 +134,6 @@ const SettingsStore: React.FC = () => {
<SettingToggle settings={settings} settingPath={['systemFont']} onChange={onToggleChange} />
</ListItem>
<div className='dyslexic'>
<ListItem label={<FormattedMessage id='preferences.fields.dyslexic_font_label' defaultMessage='Dyslexic mode' />}>
<SettingToggle settings={settings} settingPath={['dyslexicFont']} onChange={onToggleChange} />
</ListItem>
</div>
<ListItem
label={<FormattedMessage id='preferences.fields.demetricator_label' defaultMessage='Use Demetricator' />}
hint={<FormattedMessage id='preferences.hints.demetricator' defaultMessage='Decrease social media anxiety by hiding all numbers from the site.' />}

@ -1,13 +1,12 @@
import classNames from 'clsx';
import React, { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { fetchDirectory, expandDirectory } from 'soapbox/actions/directory';
import LoadMore from 'soapbox/components/load-more';
import { Column, RadioButton, Stack, Text } from 'soapbox/components/ui';
import { useAppSelector, useFeatures, useInstance } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures, useInstance } from 'soapbox/hooks';
import AccountCard from './components/account-card';
@ -21,7 +20,7 @@ const messages = defineMessages({
const Directory = () => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const { search } = useLocation();
const params = new URLSearchParams(search);
const instance = useInstance();

@ -1,13 +1,12 @@
import debounce from 'lodash/debounce';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { fetchDomainBlocks, expandDomainBlocks } from 'soapbox/actions/domain-blocks';
import Domain from 'soapbox/components/domain';
import ScrollableList from 'soapbox/components/scrollable-list';
import { Column, Spinner } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
@ -19,7 +18,7 @@ const handleLoadMore = debounce((dispatch) => {
}, 300, { leading: true });
const DomainBlocks: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const domains = useAppSelector((state) => state.domain_lists.blocks.items);

@ -78,6 +78,9 @@ describe('<FeedCarousel />', () => {
expect(screen.getAllByTestId('carousel-item-avatar')[0]).toHaveClass('ring-primary-600');
});
// HACK: wait for state change
await new Promise((r) => setTimeout(r, 0));
// Marked as seen, not selected
await userEvent.click(screen.getAllByTestId('carousel-item-avatar')[0]);
await waitFor(() => {

@ -1,11 +1,10 @@
import React, { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { authorizeFollowRequest, rejectFollowRequest } from 'soapbox/actions/accounts';
import Account from 'soapbox/components/account';
import { Button, HStack } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
const messages = defineMessages({
@ -19,7 +18,7 @@ interface IAccountAuthorize {
const AccountAuthorize: React.FC<IAccountAuthorize> = ({ id }) => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const getAccount = useCallback(makeGetAccount(), []);

@ -1,12 +1,11 @@
import debounce from 'lodash/debounce';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { fetchFollowRequests, expandFollowRequests } from 'soapbox/actions/accounts';
import ScrollableList from 'soapbox/components/scrollable-list';
import { Column, Spinner } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import AccountAuthorize from './components/account-authorize';
@ -19,7 +18,7 @@ const handleLoadMore = debounce((dispatch) => {
}, 300, { leading: true });
const FollowRequests: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const accountIds = useAppSelector((state) => state.user_lists.follow_requests.items);

@ -37,7 +37,7 @@ const List: React.FC<IList> = ({ listId }) => {
return (
<div className='flex items-center gap-1.5 px-2 py-4 text-black dark:text-white'>
<Icon src={require('@tabler/icons/list.svg')} fixedWidth />
<Icon src={require('@tabler/icons/list.svg')} />
<span className='flex-grow'>
{list.title}
</span>

@ -1,10 +1,9 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { changeListEditorTitle, submitListEditor } from 'soapbox/actions/lists';
import { Button, Form, HStack, Input } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
const messages = defineMessages({
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
@ -13,7 +12,7 @@ const messages = defineMessages({
});
const NewListForm: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const value = useAppSelector((state) => state.listEditor.get('title'));

@ -1,6 +1,5 @@
import React, { useEffect } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { createSelector } from 'reselect';
@ -9,7 +8,7 @@ import { openModal } from 'soapbox/actions/modals';
import Icon from 'soapbox/components/icon';
import ScrollableList from 'soapbox/components/scrollable-list';
import { Column, IconButton, Spinner } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import NewListForm from './components/new-list-form';
@ -35,7 +34,7 @@ const getOrderedLists = createSelector([(state: RootState) => state.lists], list
});
const Lists: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const lists = useAppSelector((state) => getOrderedLists(state));
@ -85,7 +84,7 @@ const Lists: React.FC = () => {
>
{lists.map((list: any) => (
<Link key={list.id} to={`/list/${list.id}`} className='flex items-center gap-1.5 p-2 text-gray-900 dark:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg'>
<Icon src={require('@tabler/icons/list.svg')} fixedWidth />
<Icon src={require('@tabler/icons/list.svg')} />
<span className='flex-grow'>
{list.title}
</span>

@ -1,13 +1,12 @@
import debounce from 'lodash/debounce';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { fetchMutes, expandMutes } from 'soapbox/actions/mutes';
import ScrollableList from 'soapbox/components/scrollable-list';
import { Column, Spinner } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
const messages = defineMessages({
heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
@ -18,7 +17,7 @@ const handleLoadMore = debounce((dispatch) => {
}, 300, { leading: true });
const Mutes: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const accountIds = useAppSelector((state) => state.user_lists.mutes.items);

@ -1,12 +1,11 @@
import classNames from 'clsx';
import React from 'react';
import { useDispatch } from 'react-redux';
import ReactSwipeableViews from 'react-swipeable-views';
import { endOnboarding } from 'soapbox/actions/onboarding';
import LandingGradient from 'soapbox/components/landing-gradient';
import { HStack } from 'soapbox/components/ui';
import { useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
import AvatarSelectionStep from './steps/avatar-selection-step';
import BioStep from './steps/bio-step';
@ -17,7 +16,7 @@ import FediverseStep from './steps/fediverse-step';
import SuggestedAccountsStep from './steps/suggested-accounts-step';
const OnboardingWizard = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const features = useFeatures();
const [currentStep, setCurrentStep] = React.useState<number>(0);

@ -1,11 +1,10 @@
import classNames from 'clsx';
import React from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { patchMe } from 'soapbox/actions/me';
import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
import { useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import toast from 'soapbox/toast';
import { isDefaultAvatar } from 'soapbox/utils/accounts';
import resizeImage from 'soapbox/utils/resize-image';
@ -17,7 +16,7 @@ const messages = defineMessages({
});
const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const account = useOwnAccount();
const fileInput = React.useRef<HTMLInputElement>(null);

@ -1,10 +1,9 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { patchMe } from 'soapbox/actions/me';
import { Button, Card, CardBody, FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
import { useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import toast from 'soapbox/toast';
import type { AxiosError } from 'axios';
@ -16,7 +15,7 @@ const messages = defineMessages({
const BioStep = ({ onNext }: { onNext: () => void }) => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const account = useOwnAccount();
const [value, setValue] = React.useState<string>(account?.source.get('note') || '');

@ -1,12 +1,11 @@
import classNames from 'clsx';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { patchMe } from 'soapbox/actions/me';
import StillImage from 'soapbox/components/still-image';
import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
import { useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import toast from 'soapbox/toast';
import { isDefaultHeader } from 'soapbox/utils/accounts';
import resizeImage from 'soapbox/utils/resize-image';
@ -20,7 +19,7 @@ const messages = defineMessages({
const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const account = useOwnAccount();
const fileInput = React.useRef<HTMLInputElement>(null);

@ -1,10 +1,9 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { patchMe } from 'soapbox/actions/me';
import { Button, Card, CardBody, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
import { useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import toast from 'soapbox/toast';
import type { AxiosError } from 'axios';
@ -16,7 +15,7 @@ const messages = defineMessages({
const DisplayNameStep = ({ onNext }: { onNext: () => void }) => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const account = useOwnAccount();
const [value, setValue] = React.useState<string>(account?.display_name || '');

@ -1,13 +1,12 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { changeSetting } from 'soapbox/actions/settings';
import List, { ListItem } from 'soapbox/components/list';
import { Form } from 'soapbox/components/ui';
import { SelectDropdown } from 'soapbox/features/forms';
import SettingToggle from 'soapbox/features/notifications/components/setting-toggle';
import { useFeatures, useSettings } from 'soapbox/hooks';
import { useAppDispatch, useFeatures, useSettings } from 'soapbox/hooks';
import ThemeToggle from '../ui/components/theme-toggle';
@ -89,7 +88,7 @@ const messages = defineMessages({
const Preferences = () => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const features = useFeatures();
const settings = useSettings();

@ -1,6 +1,5 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link, Redirect } from 'react-router-dom';
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
@ -8,7 +7,7 @@ import { fetchInstance } from 'soapbox/actions/instance';
import { openModal } from 'soapbox/actions/modals';
import SiteLogo from 'soapbox/components/site-logo';
import { Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui';
import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount, useInstance } from 'soapbox/hooks';
import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount, useInstance, useAppDispatch } from 'soapbox/hooks';
import Sonar from './sonar';
@ -25,7 +24,7 @@ const messages = defineMessages({
});
const Header = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const account = useOwnAccount();

@ -2,13 +2,12 @@ import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import { debounce } from 'lodash';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import { expandStatusQuotes, fetchStatusQuotes } from 'soapbox/actions/status-quotes';
import StatusList from 'soapbox/components/status-list';
import { Column } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
const messages = defineMessages({
heading: { id: 'column.quotes', defaultMessage: 'Post quotes' },
@ -18,7 +17,7 @@ const handleLoadMore = debounce((statusId: string, dispatch: React.Dispatch<any>
dispatch(expandStatusQuotes(statusId)), 300, { leading: true });
const Quotes: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const { statusId } = useParams<{ statusId: string }>();

@ -1,12 +1,11 @@
import React, { useEffect } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { fetchMfa } from 'soapbox/actions/mfa';
import List, { ListItem } from 'soapbox/components/list';
import { Card, CardBody, CardHeader, CardTitle, Column } from 'soapbox/components/ui';
import { useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
import Preferences from '../preferences';
@ -32,7 +31,7 @@ const messages = defineMessages({
/** User settings page. */
const Settings = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const history = useHistory();
const intl = useIntl();

@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
// @ts-ignore
import Overlay from 'react-overlays/lib/Overlay';
import Icon from 'soapbox/components/icon';
import ForkAwesomeIcon from 'soapbox/components/fork-awesome-icon';
import IconPickerMenu from './icon-picker-menu';
@ -68,7 +68,7 @@ const IconPickerDropdown: React.FC<IIconPickerDropdown> = ({ value, onPickEmoji
onKeyDown={onToggle}
tabIndex={0}
>
<Icon id={value} />
<ForkAwesomeIcon id={value} />
</div>
<Overlay show={active} placement={placement} target={target.current}>

@ -39,6 +39,7 @@ const messages = defineMessages({
customCssLabel: { id: 'soapbox_config.custom_css.meta_fields.url_placeholder', defaultMessage: 'URL' },
rawJSONLabel: { id: 'soapbox_config.raw_json_label', defaultMessage: 'Advanced: Edit raw JSON data' },
rawJSONHint: { id: 'soapbox_config.raw_json_hint', defaultMessage: 'Edit the settings data directly. Changes made directly to the JSON file will override the form fields above. Click "Save" to apply your changes.' },
rawJSONInvalid: { id: 'soapbox_config.raw_json_invalid', defaultMessage: 'is invalid' },
verifiedCanEditNameLabel: { id: 'soapbox_config.verified_can_edit_name_label', defaultMessage: 'Allow verified users to edit their own display name.' },
displayFqnLabel: { id: 'soapbox_config.display_fqn_label', defaultMessage: 'Display domain (eg @user@domain) for local accounts.' },
greentextLabel: { id: 'soapbox_config.greentext_label', defaultMessage: 'Enable greentext support' },
@ -394,11 +395,13 @@ const SoapboxConfig: React.FC = () => {
expanded={jsonEditorExpanded}
onToggle={toggleJSONEditor}
>
<FormGroup hintText={intl.formatMessage(messages.rawJSONHint)}>
<FormGroup
hintText={intl.formatMessage(messages.rawJSONHint)}
errors={jsonValid ? undefined : [intl.formatMessage(messages.rawJSONInvalid)]}
>
<Textarea
value={rawJSON}
onChange={handleEditJSON}
hasError={!jsonValid}
isCodeEditor
rows={12}
/>

@ -153,7 +153,7 @@ const Card: React.FC<ICard> = ({
</Stack>
);
let embed: React.ReactNode = '';
let embed: React.ReactNode = null;
const canvas = (
<Blurhash
@ -240,12 +240,6 @@ const Card: React.FC<ICard> = ({
{thumbnail}
</div>
);
} else {
embed = (
<div className='status-card__image status-card__image--empty'>
<Icon src={require('@tabler/icons/file-text.svg')} />
</div>
);
}
return (

@ -1,9 +1,9 @@
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { importFetchedStatuses } from 'soapbox/actions/importer';
import { expandTimelineSuccess } from 'soapbox/actions/timelines';
import { useAppDispatch } from 'soapbox/hooks';
import { Column } from '../../components/ui';
import Timeline from '../ui/components/timeline';
@ -31,7 +31,7 @@ const onlyMedia = false;
const TestTimeline: React.FC = () => {
const intl = useIntl();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
React.useEffect(() => {
dispatch(importFetchedStatuses(MOCK_STATUSES));

@ -48,6 +48,8 @@ describe('<UI />', () => {
await waitFor(() => {
expect(screen.getByTestId('cta-banner')).toHaveTextContent('Sign up now to discuss');
}, {
timeout: 2000,
});
});
});

@ -1,6 +1,5 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import {
followAccount,
@ -14,7 +13,7 @@ import {
} from 'soapbox/actions/accounts';
import { openModal } from 'soapbox/actions/modals';
import { Button, HStack } from 'soapbox/components/ui';
import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import type { Account as AccountEntity } from 'soapbox/types/entities';
@ -49,7 +48,7 @@ interface IActionButton {
* `actionType` prop.
*/
const ActionButton: React.FC<IActionButton> = ({ account, actionType, small }) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const features = useFeatures();
const intl = useIntl();

@ -1,13 +1,12 @@
import classNames from 'clsx';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { logOut } from 'soapbox/actions/auth';
import { Text } from 'soapbox/components/ui';
import emojify from 'soapbox/features/emoji/emoji';
import { useSoapboxConfig, useOwnAccount, useFeatures } from 'soapbox/hooks';
import { useSoapboxConfig, useOwnAccount, useFeatures, useAppDispatch } from 'soapbox/hooks';
import sourceCode from 'soapbox/utils/code';
interface IFooterLink {
@ -29,7 +28,7 @@ const LinkFooter: React.FC = (): JSX.Element => {
const features = useFeatures();
const soapboxConfig = useSoapboxConfig();
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const onClickLogOut: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(logOut());
@ -76,7 +75,7 @@ const LinkFooter: React.FC = (): JSX.Element => {
defaultMessage='{code_name} is open source software. You can contribute or report issues at {code_link} (v{code_version}).'
values={{
code_name: sourceCode.displayName,
code_link: <Text theme='subtle'><a className='underline' href={sourceCode.url} rel='noopener' target='_blank'>{sourceCode.repository}</a></Text>,
code_link: <Text theme='subtle' tag='span'><a className='underline' href={sourceCode.url} rel='noopener' target='_blank'>{sourceCode.repository}</a></Text>,
code_version: sourceCode.version,
}}
/>

@ -1,45 +0,0 @@
import React, { useEffect } from 'react';
import { NavLink } from 'react-router-dom';
import { createSelector } from 'reselect';
import { fetchLists } from 'soapbox/actions/lists';
import Icon from 'soapbox/components/icon';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import type { List as ImmutableList } from 'immutable';
import type { RootState } from 'soapbox/store';
import type { List as ListEntity } from 'soapbox/types/entities';
const getOrderedLists = createSelector([(state: RootState) => state.lists], lists => {
if (!lists) {
return lists;
}
return lists.toList().filter(item => !!item).sort((a, b) => (a as ListEntity).title.localeCompare((b as ListEntity).title)).take(4) as ImmutableList<ListEntity>;
});
const ListPanel = () => {
const dispatch = useAppDispatch();
const lists = useAppSelector((state) => getOrderedLists(state));
useEffect(() => {
dispatch(fetchLists());
}, []);
if (!lists || lists.isEmpty()) {
return null;
}
return (
<div>
<hr />
{lists.map(list => (
<NavLink key={list.id} className='column-link column-link--transparent' strict to={`/list/${list.id}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.title}</NavLink>
))}
</div>
);
};
export default ListPanel;

@ -4,7 +4,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 { normalizeAccount, normalizeStatus } from '../../../../../../normalizers';
import ReportModal from '../report-modal';
@ -64,6 +64,9 @@ describe('<ReportModal />', () => {
await user.click(screen.getByTestId('rule-1'));
await user.click(screen.getByText(/Next/));
await user.click(screen.getByText(/Submit/));
await waitFor(() => {
expect(screen.getByText(/Thanks for submitting your report/)).toBeInTheDocument();
});
});
});

@ -1,14 +1,13 @@
import { OrderedSet } from 'immutable';
import React, { useEffect, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import Toggle from 'react-toggle';
import { changeReportBlock, changeReportForward } from 'soapbox/actions/reports';
import { fetchRules } from 'soapbox/actions/rules';
import { Button, FormGroup, HStack, Stack, Text } from 'soapbox/components/ui';
import StatusCheckBox from 'soapbox/features/report/components/status-check-box';
import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { isRemote, getDomain } from 'soapbox/utils/accounts';
import type { ReducerAccount } from 'soapbox/reducers/accounts';
@ -26,7 +25,7 @@ interface IOtherActionsStep {
}
const OtherActionsStep = ({ account }: IOtherActionsStep) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const features = useFeatures();
const intl = useIntl();

@ -1,12 +1,11 @@
import classNames from 'clsx';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { changeReportComment, changeReportRule } from 'soapbox/actions/reports';
import { fetchRules } from 'soapbox/actions/rules';
import { FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import type { ReducerAccount } from 'soapbox/reducers/accounts';
@ -22,7 +21,7 @@ interface IReasonStep {
const RULES_HEIGHT = 385;
const ReasonStep = (_props: IReasonStep) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const rulesListRef = useRef(null);

@ -1,7 +1,6 @@
import classNames from 'clsx';
import React, { useRef, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link, Redirect } from 'react-router-dom';
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
@ -10,7 +9,7 @@ import { openSidebar } from 'soapbox/actions/sidebar';
import SiteLogo from 'soapbox/components/site-logo';
import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui';
import Search from 'soapbox/features/compose/components/search';
import { useOwnAccount, useSoapboxConfig } from 'soapbox/hooks';
import { useAppDispatch, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks';
import ProfileDropdown from './profile-dropdown';
@ -24,7 +23,7 @@ const messages = defineMessages({
});
const Navbar = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const intl = useIntl();
const node = useRef(null);

@ -70,6 +70,7 @@ const PendingStatus: React.FC<IPendingStatus> = ({ idempotencyKey, className, mu
account={account}
timestamp={status.created_at}
hideActions
withLinkToProfile={false}
/>
</HStack>
</div>

@ -1,13 +1,12 @@
import throttle from 'lodash/throttle';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth';
import Account from 'soapbox/components/account';
import { Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList } from 'soapbox/components/ui';
import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
import ThemeToggle from './theme-toggle';
@ -35,7 +34,7 @@ type IMenuItem = {
const getAccount = makeGetAccount();
const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const features = useFeatures();
const intl = useIntl();

@ -1,7 +1,6 @@
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import React, { useEffect } from 'react';
import { FormattedList, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchAccountFamiliarFollowers } from 'soapbox/actions/familiar-followers';
@ -9,7 +8,7 @@ import { openModal } from 'soapbox/actions/modals';
import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper';
import { Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification-badge';
import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
import type { Account } from 'soapbox/types/entities';
@ -21,7 +20,7 @@ interface IProfileFamiliarFollowers {
}
const ProfileFamiliarFollowers: React.FC<IProfileFamiliarFollowers> = ({ account }) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const me = useAppSelector((state) => state.me);
const features = useFeatures();
const familiarFollowerIds = useAppSelector(state => state.user_lists.familiar_followers.get(account.id)?.items || ImmutableOrderedSet<string>());

@ -1,12 +1,11 @@
import { List as ImmutableList } from 'immutable';
import React, { useState, useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { openModal } from 'soapbox/actions/modals';
import { expandAccountMediaTimeline } from 'soapbox/actions/timelines';
import { Spinner, Text, Widget } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { getAccountGallery } from 'soapbox/selectors';
import MediaItem from '../../account-gallery/components/media-item';
@ -18,7 +17,7 @@ interface IProfileMediaPanel {
}
const ProfileMediaPanel: React.FC<IProfileMediaPanel> = ({ account }) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const [loading, setLoading] = useState(true);

@ -1,6 +1,6 @@
import React from 'react';
import Icon from 'soapbox/components/icon';
import ForkAwesomeIcon from 'soapbox/components/fork-awesome-icon';
import { Widget, Stack, Text } from 'soapbox/components/ui';
import { useInstance, useSettings, useSoapboxConfig } from 'soapbox/hooks';
@ -20,7 +20,7 @@ const PromoPanel: React.FC = () => {
{promoItems.map((item, i) => (
<Text key={i}>
<a className='flex items-center' href={item.url} target='_blank'>
<Icon id={item.icon} className='flex-none text-lg mr-2 rtl:mr-0 rtl:ml-2' fixedWidth />
<ForkAwesomeIcon id={item.icon} className='flex-none text-lg mr-2 rtl:mr-0 rtl:ml-2' fixedWidth />
{item.textLocales.get(locale) || item.text}
</a>
</Text>

@ -1,14 +1,13 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { changeSetting } from 'soapbox/actions/settings';
import { useSettings } from 'soapbox/hooks';
import { useAppDispatch, useSettings } from 'soapbox/hooks';
import ThemeSelector from './theme-selector';
/** Stateful theme selector. */
const ThemeToggle: React.FC = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const themeMode = useSettings().get('themeMode');
const handleChange = (themeMode: string) => {

@ -26,10 +26,10 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
const fqn = useAppSelector((state) => displayFqn(state));
if (!account) return null;
const displayNameHtml = { __html: account.get('display_name_html') };
const acct = !account.get('acct').includes('@') && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
const header = account.get('header');
const verified = account.get('verified');
const displayNameHtml = { __html: account.display_name_html };
const acct = !account.acct.includes('@') && domain ? `${account.acct}@${domain}` : account.acct;
const header = account.header;
const verified = account.verified;
return (
<div className='relative'>
@ -43,11 +43,11 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
<HStack justifyContent='between'>
<Link
to={`/@${account.get('acct')}`}
to={`/@${account.acct}`}
title={acct}
className='-mt-12 block'
>
<Avatar src={account.avatar} className='h-20 w-20 bg-gray-50 ring-2 ring-white overflow-hidden' />
<Avatar src={account.avatar} size={80} className='h-20 w-20 bg-gray-50 ring-2 ring-white overflow-hidden' />
</Link>
{action && (
@ -57,7 +57,7 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
</Stack>
<Stack>
<Link to={`/@${account.get('acct')}`}>
<Link to={`/@${account.acct}`}>
<HStack space={1} alignItems='center'>
<Text size='lg' weight='bold' dangerouslySetInnerHTML={displayNameHtml} />
@ -77,11 +77,11 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
</Stack>
<HStack alignItems='center' space={3}>
{account.get('followers_count') >= 0 && (
<Link to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
{account.followers_count >= 0 && (
<Link to={`/@${account.acct}/followers`} title={intl.formatNumber(account.followers_count)}>
<HStack alignItems='center' space={1}>
<Text theme='primary' weight='bold' size='sm'>
{shortNumberFormat(account.get('followers_count'))}
{shortNumberFormat(account.followers_count)}
</Text>
<Text weight='bold' size='sm'>
<FormattedMessage id='account.followers' defaultMessage='Followers' />
@ -90,11 +90,11 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
</Link>
)}
{account.get('following_count') >= 0 && (
<Link to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
{account.following_count >= 0 && (
<Link to={`/@${account.acct}/following`} title={intl.formatNumber(account.following_count)}>
<HStack alignItems='center' space={1}>
<Text theme='primary' weight='bold' size='sm'>
{shortNumberFormat(account.get('following_count'))}
{shortNumberFormat(account.following_count)}
</Text>
<Text weight='bold' size='sm'>
<FormattedMessage id='account.follows' defaultMessage='Follows' />

@ -472,7 +472,7 @@ const UI: React.FC = ({ children }) => {
navigator.serviceWorker.addEventListener('message', handleServiceWorkerPostMessage);
}
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
if (window.Notification?.permission === 'default') {
window.setTimeout(() => Notification.requestPermission(), 120 * 1000);
}

@ -30,7 +30,10 @@ describe('<Registration />', () => {
fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} });
});
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent(/welcome to/i);
});
expect(screen.queryAllByRole('heading')).toHaveLength(0);
});
});
@ -38,7 +41,11 @@ describe('<Registration />', () => {
describe('with invalid data', () => {
it('handles 422 errors', async() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/accounts').reply(422, {});
mock.onPost('/api/v1/pepe/accounts').reply(
422, {
error: 'user_taken',
},
);
});
render(<Registration />);
@ -47,8 +54,32 @@ describe('<Registration />', () => {
fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} });
});
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent(/this username has already been taken/i);
});
});
it('handles 422 errors with messages', async() => {
__stub(mock => {
mock.onPost('/api/v1/pepe/accounts').reply(
422, {
error: 'user_vip',
message: 'This username is unavailable.',
},
);
});
render(<Registration />);
await waitFor(() => {
fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} });
});
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent(/this username is unavailable/i);
});
});
it('handles generic errors', async() => {
__stub(mock => {
@ -61,9 +92,11 @@ describe('<Registration />', () => {
fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} });
});
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent(/failed to register your account/i);
});
});
});
describe('validations', () => {
it('should undisable button with valid password', async() => {

@ -58,9 +58,11 @@ const Registration = () => {
intl.formatMessage(messages.success, { siteTitle: instance.title }),
);
})
.catch((error: AxiosError) => {
if (error?.response?.status === 422) {
toast.error(intl.formatMessage(messages.usernameTaken));
.catch((errorResponse: AxiosError<{ error: string, message: string }>) => {
const error = errorResponse.response?.data?.error;
if (error) {
toast.error(errorResponse.response?.data?.message || intl.formatMessage(messages.usernameTaken));
} else {
toast.error(intl.formatMessage(messages.error));
}

@ -60,7 +60,9 @@ describe('<EmailVerification />', () => {
);
});
await waitFor(() => {
expect(screen.getByTestId('form-group-error')).toHaveTextContent('is taken');
});
});
});
});

@ -37,8 +37,10 @@ describe('<SmsVerification />', () => {
);
});
await waitFor(() => {
expect(screen.getByRole('heading')).toHaveTextContent('Verification code');
expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.');
});
act(() => {
toast.remove();
@ -68,8 +70,10 @@ describe('<SmsVerification />', () => {
);
});
await waitFor(() => {
expect(screen.getByRole('heading')).toHaveTextContent('Verification code');
expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.');
});
act(() => {
toast.remove();
@ -82,9 +86,11 @@ describe('<SmsVerification />', () => {
await userEvent.type(screen.getByLabelText('Digit 5'), '5');
await userEvent.type(screen.getByLabelText('Digit 6'), '6');
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent('Your SMS token has expired.');
});
});
});
describe('with invalid data', () => {
beforeEach(() => {
@ -106,7 +112,9 @@ describe('<SmsVerification />', () => {
);
});
await waitFor(() => {
expect(screen.getByTestId('toast')).toHaveTextContent('Failed to send SMS message to your phone number.');
});
});
});
});

@ -1,6 +1,5 @@
import React, { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { logOut } from 'soapbox/actions/auth';
@ -8,10 +7,10 @@ import { openModal } from 'soapbox/actions/modals';
import LandingGradient from 'soapbox/components/landing-gradient';
import SiteLogo from 'soapbox/components/site-logo';
import { Button, Stack, Text } from 'soapbox/components/ui';
import { useInstance, useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useInstance, useOwnAccount } from 'soapbox/hooks';
const WaitlistPage = () => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const instance = useInstance();
const me = useOwnAccount();

@ -1,7 +1,7 @@
import classNames from 'clsx';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import Blurhash from 'soapbox/components/blurhash';
@ -159,7 +159,7 @@ const Video: React.FC<IVideo> = ({
}
};
useEffect(() => {
useLayoutEffect(() => {
setDimensions();
}, [player.current]);

@ -25,7 +25,7 @@ const useDimensions = (): UseDimensionsResult => {
);
useEffect((): any => {
if (!element) return null;
if (!element) return;
observer.observe(element);
return () => {

@ -1,5 +1,6 @@
'use strict';
import { act } from '@testing-library/react';
import { toast } from 'react-hot-toast';
import { __clear as clearApiMocks } from '../api/__mocks__';
@ -17,8 +18,10 @@ require('fake-indexeddb/auto');
// Clear toasts after each test.
afterEach(() => {
act(() => {
toast.remove();
});
});
const intersectionObserverMock = () => ({ observe: () => null, disconnect: () => null });
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);

@ -1,46 +0,0 @@
'use strict';
// Convenience function to load polyfills and return a promise when it's done.
// If there are no polyfills, then this is just Promise.resolve() which means
// it will execute in the same tick of the event loop (i.e. near-instant).
function importBasePolyfills() {
return import(/* webpackChunkName: "base_polyfills" */ './base-polyfills');
}
function importExtraPolyfills() {
return import(/* webpackChunkName: "extra_polyfills" */ './extra-polyfills');
}
function loadPolyfills() {
const needsBasePolyfills = !(
// @ts-ignore
Array.prototype.includes &&
// @ts-ignore
HTMLCanvasElement.prototype.toBlob &&
window.Intl &&
// @ts-ignore
Number.isNaN &&
// @ts-ignore
Object.assign &&
// @ts-ignore
Object.values &&
window.Symbol
);
// 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
);
return Promise.all([
needsBasePolyfills && importBasePolyfills(),
needsExtraPolyfills && importExtraPolyfills(),
]);
}
export default loadPolyfills;

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "الدعم",
"alert.unexpected.message": "حدث خطأ ما.",
"alert.unexpected.return_home": "العودة للصفحة الرئيسة",
"alert.unexpected.title": "المعذرة!",
"aliases.account.add": "إنشاء اسم مستعار",
"aliases.account_label": "الحساب القديم:",
"aliases.aliases_list_delete": "إلغاء ربط الاسم المستعار",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "حدث خطأ في أثناء تحميل الصفحة.",
"bundle_modal_error.retry": "إعادة المحاولة",
"card.back.label": "العودة",
"chat_box.actions.send": "إرسال",
"chat_box.input.placeholder": "إرسال رسالة…",
"chat_panels.main_window.empty": "لا توجد رسائل، لبدء المحادثات زُر المِلف الشخصي لمستخدم ما.",
"chat_panels.main_window.title": "المحادثات",
"chat_window.close": "إغلاق المحادثة",
"chats.actions.delete": "حذف الرسالة",
"chats.actions.more": "المزيد",
"chats.actions.report": "الإبلاغ عن المستخدم",
"chats.attachment": "مُرفق",
"chats.attachment_image": "صورة",
"chats.audio_toggle_off": "الإشعارات الصوتية غير مُفعّلة",
"chats.audio_toggle_on": "الإشعارات الصوتية مُفعّلة",
"chats.dividers.today": "اليوم",
"chats.search_placeholder": "بَدْء دردشة مع…",
"column.admin.awaiting_approval": "في انتظار الموافقة",
@ -270,13 +260,11 @@
"column.public": "الاتحاد الاجتماعي",
"column.reactions": "تفاعلات",
"column.reblogs": "إعادة النشر",
"column.remote": "الاتحاد الاجتماعي",
"column.scheduled_statuses": "منشورات مُجدولة",
"column.search": "البحث",
"column.settings_store": "مخزن الإعدادات",
"column.soapbox_config": "تهيئة بسّام",
"column.test": "تجربة الخط الزمني",
"column_back_button.label": "الرجوع",
"column_forbidden.body": "ليست لديك الصلاحيات للدخول إلى هذه الصفحة.",
"column_forbidden.title": "محظور",
"common.cancel": "إلغاء",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "إلغاء جدولة المنشور",
"confirmations.scheduled_status_delete.message": "هل تود حقا حذف هذا المنشور المجدول",
"confirmations.unfollow.confirm": "إلغاء المتابعة",
"confirmations.unfollow.heading": "إلغاء متابعة {name}",
"confirmations.unfollow.message": "هل تود حقًّا إلغاء متابعة {name}؟",
"crypto_donate.explanation_box.message": "{siteTitle} يقبل العملات الرقمية . بإمكانك التبرع عبر أي من هذه العناوين في الأسفل . شكرا لدعمك!",
"crypto_donate.explanation_box.title": "يتم إرسال العملات الرقمية",
"crypto_donate_panel.actions.view": "انقر لإظهار {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "اخف الوسائط المصنفة بحساس",
"preferences.fields.display_media.hide_all": "اخف جميع الوسائط",
"preferences.fields.display_media.show_all": "اظهر جميع الوسائط",
"preferences.fields.dyslexic_font_label": "وضع عسر القراءة",
"preferences.fields.expand_spoilers_label": "توسيع المنشورات المعلّمة بتحذير دائمًا",
"preferences.fields.language_label": "لغة الواجهة",
"preferences.fields.media_display_label": "عرض الوسائط",
@ -834,7 +819,6 @@
"preferences.fields.underline_links_label": "إظهار الروابط في المنشورات من خلال وضع خط تحت الرابط",
"preferences.fields.unfollow_modal_label": "أظهار إشعار لتأكيد إلغاء المتابعة قبل التنفيذ",
"preferences.hints.demetricator": "تقليل قلق التواصل الاجتماعي من خلال إخفاء جميع الأرقام في المنصّة.",
"preferences.hints.feed": "في الخط الزمني الخاص بك",
"preferences.notifications.advanced": "عرض جميع تصنيفات الإشعارات",
"preferences.options.content_type_markdown": "ماركداون",
"preferences.options.content_type_plaintext": "نص عادي",
@ -1016,7 +1000,6 @@
"sms_verification.sent.body": "لقد أرسلنا لك رمزًا مكونًا من 6 أرقام عبر رسالة نصية قصيرة. رجاءً أدخله في الأسفل.",
"sms_verification.sent.header": "تأكيد الرمز",
"sms_verification.success": "تم إرسال رمز التحقق إلى رقم هاتفك.",
"toast.view": "عرض",
"soapbox_config.authenticated_profile_hint": "على المستخدمين أن يُسجلوا دخولهم كي يتمكنوا من عرض الردود والوسائط على الحسابات الشخصية للمستخدمين.",
"soapbox_config.authenticated_profile_label": "حسابات شخصية تتطلب تسجيل الدخول لتصفحها",
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "حاشية حقوق الطبع والنشر",
@ -1029,8 +1012,6 @@
"soapbox_config.display_fqn_label": "عرض اسم النطاق للحسابات المحلية (مثال zaki@instance)",
"soapbox_config.feed_injection_hint": "حقن الخط الزمني بمحتوى إضافي مثل حسابات مقترحة.",
"soapbox_config.feed_injection_label": "حقن الخط الزمني",
"soapbox_config.fields.accent_color_label": "لون التمييز",
"soapbox_config.fields.brand_color_label": "لون العلامة التجارية",
"soapbox_config.fields.crypto_addresses_label": "عناوين العملات الافتراضية",
"soapbox_config.fields.home_footer_fields_label": "العناصر في حاشية الصفحة الرئيسية",
"soapbox_config.fields.logo_label": "الشعار",
@ -1139,7 +1120,6 @@
"sw.update_text": "هناك تحديث متوفر",
"sw.url": "رابط الإسكربت",
"tabs_bar.all": "الكل",
"tabs_bar.chats": "المحادثات",
"tabs_bar.dashboard": "لوحة التحكم",
"tabs_bar.fediverse": "الكون الفيدرالي الإجتماعي",
"tabs_bar.home": "الرئيسية",
@ -1161,10 +1141,10 @@
"time_remaining.minutes": "{number, plural, one {# دقيقة} other {# دقائق}} متبقية",
"time_remaining.moments": "لحظات متبقية",
"time_remaining.seconds": "{number, plural, one {# ثانية} other {# ثوانٍ}} متبقية",
"toast.view": "عرض",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} آخرون {people}} يتحدثون",
"trends.title": "الشائع",
"trendsPanel.viewAll": "إظهار الكل",
"ui.beforeunload": "محتوى المسودة سيضيع اذا خرجت",
"unauthorized_modal.text": "يجب عليك تسجيل الدخول لتتمكن من القيام بذلك.",
"unauthorized_modal.title": "التسجيل في {site_title}",
"upload_area.title": "اسحب ملف وافلته لتحميله",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Asocedió un fallu inesperáu.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "¡Ups!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"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",
"chats.actions.delete": "Delete message",
"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.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
@ -270,13 +260,11 @@
"column.public": "Llinia temporal federada",
"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": "Atrás",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "¿De xuru que quies dexar de siguir a {name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Hide media marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide media",
"preferences.fields.display_media.show_all": "Always show media",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings",
"preferences.fields.language_label": "Language",
"preferences.fields.media_display_label": "Media display",
@ -834,7 +819,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",
@ -1016,7 +1000,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,8 +1012,6 @@
"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.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,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": "Aniciu",
@ -1161,10 +1141,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": "El borrador va perdese si coles de Soapbox.",
"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",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "An unexpected error occurred.",
"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,18 +185,9 @@
"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",
"chats.actions.delete": "Delete message",
"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.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
@ -270,13 +260,11 @@
"column.public": "Публичен канал",
"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": "Назад",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Hide media marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide media",
"preferences.fields.display_media.show_all": "Always show media",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings",
"preferences.fields.language_label": "Language",
"preferences.fields.media_display_label": "Media display",
@ -834,7 +819,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",
@ -1016,7 +1000,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,8 +1012,6 @@
"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.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,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": "Начало",
@ -1161,10 +1141,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",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "অপ্রত্যাশিত একটি সমস্যা হয়েছে।",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "ওহো!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "এই অংশটি দেখাতে যেয়ে কোনো সমস্যা হয়েছে।",
"bundle_modal_error.retry": "আবার চেষ্টা করুন",
"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",
"chats.actions.delete": "Delete message",
"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.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
@ -270,13 +260,11 @@
"column.public": "যুক্ত সময়রেখা",
"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": "পেছনে",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "অনুসরণ করা বাতিল করতে",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "আপনি কি নিশ্চিত {name} কে আর অনুসরণ করতে চান না ?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Hide media marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide media",
"preferences.fields.display_media.show_all": "Always show media",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings",
"preferences.fields.language_label": "Language",
"preferences.fields.media_display_label": "Media display",
@ -834,7 +819,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",
@ -1016,7 +1000,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,8 +1012,6 @@
"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.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,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": "বাড়ি",
@ -1161,10 +1141,10 @@
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} বাকি আছে",
"time_remaining.moments": "সময় বাকি আছে",
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} বাকি আছে",
"toast.view": "View",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} কথা বলছে",
"trends.title": "Trends",
"trendsPanel.viewAll": "View all",
"ui.beforeunload": "যে পর্যন্ত এটা লেখা হয়েছে, Soapbox থেকে চলে গেলে এটা মুছে যাবে।",
"unauthorized_modal.text": "You need to be logged in to do that.",
"unauthorized_modal.title": "Sign up for {site_title}",
"upload_area.title": "টেনে এখানে ছেড়ে দিলে এখানে যুক্ত করা যাবে",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "C'hem !",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "Something went wrong while loading this modal.",
"bundle_modal_error.retry": "Klask endro",
"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",
"chats.actions.delete": "Delete message",
"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.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
@ -270,13 +260,11 @@
"column.public": "Federated timeline",
"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",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Hide media marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide media",
"preferences.fields.display_media.show_all": "Always show media",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings",
"preferences.fields.language_label": "Language",
"preferences.fields.media_display_label": "Media display",
@ -834,7 +819,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",
@ -1016,7 +1000,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,8 +1012,6 @@
"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.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,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",
@ -1161,10 +1141,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",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "S'ha produït un error inesperat.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "Vaja!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "S'ha produït un error en carregar aquest component.",
"bundle_modal_error.retry": "Torna-ho a provar",
"card.back.label": "Back",
"chat_box.actions.send": "Envia",
"chat_box.input.placeholder": "Envia un missatge…",
"chat_panels.main_window.empty": "Cap xat trobat. Per arrencar un xat, visita el perfil d'un usuari.",
"chat_panels.main_window.title": "Xats",
"chat_window.close": "Close chat",
"chats.actions.delete": "Elimina missatge",
"chats.actions.more": "Més",
"chats.actions.report": "Denunciar usuari",
"chats.attachment": "Attachment",
"chats.attachment_image": "Image",
"chats.audio_toggle_off": "Notificació amb so desactivada",
"chats.audio_toggle_on": "Notificació amb so activada",
"chats.dividers.today": "Avui",
"chats.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Esperant aprovació",
@ -270,13 +260,11 @@
"column.public": "Línia de temps federada",
"column.reactions": "Reactions",
"column.reblogs": "Reposts",
"column.remote": "Línia de temps federada",
"column.scheduled_statuses": "Scheduled Posts",
"column.search": "Search",
"column.settings_store": "Settings store",
"column.soapbox_config": "Configuració de Soapbox",
"column.test": "Test timeline",
"column_back_button.label": "Enrere",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "Deixa de seguir",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "Estàs segur que vols deixar de seguir {name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Amaga les imatges marcades com a sensibles",
"preferences.fields.display_media.hide_all": "Oculta sempre les imatges",
"preferences.fields.display_media.show_all": "Mostra sempre les imatges",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Expandeix sempre els missatges marcats amb avisos de contingut",
"preferences.fields.language_label": "Llengua",
"preferences.fields.media_display_label": "Visualització multimèdia",
@ -834,7 +819,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",
@ -1016,7 +1000,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": "Peu de pàgina dels drets d'autor",
@ -1029,8 +1012,6 @@
"soapbox_config.display_fqn_label": "Mostra el domini (eg @user@domain) per als comptes locals.",
"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": "Color de la marca",
"soapbox_config.fields.crypto_addresses_label": "Cryptocurrency addresses",
"soapbox_config.fields.home_footer_fields_label": "Elements de peu de pàgina d'inici",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,6 @@
"sw.update_text": "An update is available.",
"sw.url": "Script URL",
"tabs_bar.all": "All",
"tabs_bar.chats": "Xats",
"tabs_bar.dashboard": "Tauler",
"tabs_bar.fediverse": "Fediverse",
"tabs_bar.home": "Inici",
@ -1161,10 +1141,10 @@
"time_remaining.minutes": "{number, plural, one {# minut} other {# minuts}} restants",
"time_remaining.moments": "Moments restants",
"time_remaining.seconds": "{number, plural, one {# segon} other {# segons}} restants",
"toast.view": "View",
"trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {persones}} parlant-hi",
"trends.title": "Tendències",
"trendsPanel.viewAll": "View all",
"ui.beforeunload": "El teu esborrany es perdrà si surts de Soapbox.",
"unauthorized_modal.text": "Heu d'iniciar sessió per fer això.",
"unauthorized_modal.title": "Registrar-se a {site_title}",
"upload_area.title": "Arrossega i deixa anar per a carregar",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "Uups!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "C'hè statu un prublemu caricandu st'elementu.",
"bundle_modal_error.retry": "Pruvà torna",
"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",
"chats.actions.delete": "Delete message",
"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.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
@ -270,13 +260,11 @@
"column.public": "Linea pubblica glubale",
"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": "Ritornu",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "Disabbunassi",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Hide media marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide media",
"preferences.fields.display_media.show_all": "Always show media",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings",
"preferences.fields.language_label": "Language",
"preferences.fields.media_display_label": "Media display",
@ -834,7 +819,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",
@ -1016,7 +1000,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,8 +1012,6 @@
"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.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,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": "Accolta",
@ -1161,10 +1141,10 @@
"time_remaining.minutes": "{number, plural, one {# minuta ferma} other {# minute fermanu}} left",
"time_remaining.moments": "Ci fermanu qualchi mumentu",
"time_remaining.seconds": "{number, plural, one {# siconda ferma} other {# siconde fermanu}}",
"toast.view": "View",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} parlanu",
"trends.title": "Trends",
"trendsPanel.viewAll": "View all",
"ui.beforeunload": "A bruttacopia sarà persa s'ellu hè chjosu Soapbox.",
"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 per caricà un fugliale",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Došlo k neočekávané chybě.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "Jejda!",
"aliases.account.add": "Vytvořit alias",
"aliases.account_label": "Starý účet:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "Při načítání tohoto komponentu se něco pokazilo.",
"bundle_modal_error.retry": "Zkusit znovu",
"card.back.label": "Back",
"chat_box.actions.send": "Poslat",
"chat_box.input.placeholder": "Poslat zprávu…",
"chat_panels.main_window.empty": "Žádné chaty nenalezeny. Chat můžete začít na něčím profilu.",
"chat_panels.main_window.title": "Chaty",
"chat_window.close": "Close chat",
"chats.actions.delete": "Odstranit zprávu",
"chats.actions.more": "Více",
"chats.actions.report": "Nahlásit uživatele",
"chats.attachment": "Attachment",
"chats.attachment_image": "Image",
"chats.audio_toggle_off": "Audio upozornění vypnuté",
"chats.audio_toggle_on": "Audio upozornění zapnoté",
"chats.dividers.today": "Dnes",
"chats.search_placeholder": "Chatovat s…",
"column.admin.awaiting_approval": "Čeká na schválení",
@ -270,13 +260,11 @@
"column.public": "Federovaná časová osa",
"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 nastavení",
"column.test": "Test timeline",
"column_back_button.label": "Zpět",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Zrušit",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Zrušit naplánovaný příspěvek",
"confirmations.scheduled_status_delete.message": "Určitě chcete tento naplánovaný příspěvěk zrušit?",
"confirmations.unfollow.confirm": "Přestat sledovat",
"confirmations.unfollow.heading": "Přestat selovat uživatele {name}",
"confirmations.unfollow.message": "jste si jistý/á, že chcete přestat sledovat uživatele {name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Skrývat média označená jako citlivá",
"preferences.fields.display_media.hide_all": "Vždy skrývat média",
"preferences.fields.display_media.show_all": "Vždy zobrazovat média",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Vždy rozbalit příspěvky označené varováním",
"preferences.fields.language_label": "Jazyk",
"preferences.fields.media_display_label": "Zobrazování médií",
@ -834,7 +819,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": "Ve vaší časové ose",
"preferences.notifications.advanced": "Show all notification categories",
"preferences.options.content_type_markdown": "Markdown",
"preferences.options.content_type_plaintext": "Prostý text",
@ -1016,7 +1000,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": "Pouze přihlášení uživatelé mohou prohlížet odpovědi a média na uživatelských profilech.",
"soapbox_config.authenticated_profile_label": "Profily vyžadují přihlášení",
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright v zápatí",
@ -1029,8 +1012,6 @@
"soapbox_config.display_fqn_label": "Zobrazit doménu (např @uzivatel@domena) u místních účtů.",
"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": "Vedlejší barva",
"soapbox_config.fields.brand_color_label": "Hlavní barva",
"soapbox_config.fields.crypto_addresses_label": "Adresy kryptoměn",
"soapbox_config.fields.home_footer_fields_label": "Zápatí časové osy",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,6 @@
"sw.update_text": "Je dostupná nová verze.",
"sw.url": "Script URL",
"tabs_bar.all": "All",
"tabs_bar.chats": "Chaty",
"tabs_bar.dashboard": "Ovládací panel",
"tabs_bar.fediverse": "Fediverse",
"tabs_bar.home": "Domů",
@ -1161,10 +1141,10 @@
"time_remaining.minutes": "{number, plural, one {Zbývá # minuta} few {Zbývají # minuty} many {Zbývá # minuty} other {Zbývá # minut}}",
"time_remaining.moments": "Zbývá několik sekund",
"time_remaining.seconds": "{number, plural, one {Zbývá # sekunda} few {Zbývají # sekundy} many {Zbývá # sekundy} other {Zbývá # sekund}}",
"toast.view": "View",
"trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} few {lidé} many {lidí} other {lidí}} hovoří",
"trends.title": "Trendy",
"trendsPanel.viewAll": "View all",
"ui.beforeunload": "Váš koncept se ztratí, pokud Soapbox opustíte.",
"unauthorized_modal.text": "Nejprve se přihlašte.",
"unauthorized_modal.title": "Registrovat se na {site_title}",
"upload_area.title": "Přetažením nahrajete",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "Wps!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
"bundle_modal_error.retry": "Ceiswich eto",
"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",
"chats.actions.delete": "Delete message",
"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.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
@ -270,13 +260,11 @@
"column.public": "Ffrwd y ffederasiwn",
"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": "Nôl",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "Dad-ddilynwch",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Hide media marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide media",
"preferences.fields.display_media.show_all": "Always show media",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings",
"preferences.fields.language_label": "Language",
"preferences.fields.media_display_label": "Media display",
@ -834,7 +819,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",
@ -1016,7 +1000,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,8 +1012,6 @@
"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.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,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": "Hafan",
@ -1161,10 +1141,10 @@
"time_remaining.minutes": "{number, plural, one {# funud} other {# o funudau}} ar ôl",
"time_remaining.moments": "Munudau ar ôl",
"time_remaining.seconds": "{number, plural, one {# eiliad} other {# o eiliadau}} ar ôl",
"toast.view": "View",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} yn siarad",
"trends.title": "Trends",
"trendsPanel.viewAll": "View all",
"ui.beforeunload": "Mi fyddwch yn colli eich drafft os gadewch Soapbox.",
"unauthorized_modal.text": "You need to be logged in to do that.",
"unauthorized_modal.title": "Sign up for {site_title}",
"upload_area.title": "Llusgwch & gollwing i uwchlwytho",

@ -147,7 +147,6 @@
"alert.unexpected.links.support": "Support",
"alert.unexpected.message": "Der opstod en uventet fejl.",
"alert.unexpected.return_home": "Return Home",
"alert.unexpected.title": "Ups!",
"aliases.account.add": "Create alias",
"aliases.account_label": "Old account:",
"aliases.aliases_list_delete": "Unlink alias",
@ -186,18 +185,9 @@
"bundle_modal_error.message": "Noget gik galt under indlæsningen af dette komponent.",
"bundle_modal_error.retry": "Prøv igen",
"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",
"chats.actions.delete": "Delete message",
"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.search_placeholder": "Start a chat with…",
"column.admin.awaiting_approval": "Awaiting Approval",
@ -270,13 +260,11 @@
"column.public": "Fælles tidslinje",
"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": "Tilbage",
"column_forbidden.body": "You do not have permission to access this page.",
"column_forbidden.title": "Forbidden",
"common.cancel": "Cancel",
@ -372,8 +360,6 @@
"confirmations.scheduled_status_delete.heading": "Cancel scheduled post",
"confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?",
"confirmations.unfollow.confirm": "Følg ikke længere",
"confirmations.unfollow.heading": "Unfollow {name}",
"confirmations.unfollow.message": "Er du sikker på, du ikke længere vil følge {name}?",
"crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!",
"crypto_donate.explanation_box.title": "Sending cryptocurrency donations",
"crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}",
@ -822,7 +808,6 @@
"preferences.fields.display_media.default": "Hide media marked as sensitive",
"preferences.fields.display_media.hide_all": "Always hide media",
"preferences.fields.display_media.show_all": "Always show media",
"preferences.fields.dyslexic_font_label": "Dyslexic mode",
"preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings",
"preferences.fields.language_label": "Language",
"preferences.fields.media_display_label": "Media display",
@ -834,7 +819,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",
@ -1016,7 +1000,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,8 +1012,6 @@
"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.home_footer_fields_label": "Home footer items",
"soapbox_config.fields.logo_label": "Logo",
@ -1139,7 +1120,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": "Hjem",
@ -1161,10 +1141,10 @@
"time_remaining.minutes": "{number, plural, one {# minut} other {# minutter}} tilbage",
"time_remaining.moments": "Få øjeblikke tilbage",
"time_remaining.seconds": "{number, plural, one {# sekund} other {# sekunder}} tilbage",
"toast.view": "View",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {personer}} snakker",
"trends.title": "Trends",
"trendsPanel.viewAll": "View all",
"ui.beforeunload": "Din kladde vil gå tabt hvis du forlader Soapbox.",
"unauthorized_modal.text": "You need to be logged in to do that.",
"unauthorized_modal.title": "Sign up for {site_title}",
"upload_area.title": "Træk og slip for at uploade",

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save