pl-fe: add option to disable displaying user-provided media
Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -14,6 +14,7 @@ import VerificationBadge from 'pl-fe/components/verification-badge';
|
||||
import Emojify from 'pl-fe/features/emoji/emojify';
|
||||
import ActionButton from 'pl-fe/features/ui/components/action-button';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
import { getAcct } from 'pl-fe/utils/accounts';
|
||||
import { displayFqn } from 'pl-fe/utils/state';
|
||||
|
||||
@ -140,6 +141,7 @@ const Account = ({
|
||||
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null);
|
||||
const { disableUserProvidedMedia } = useSettings();
|
||||
|
||||
const handleAction = () => {
|
||||
onActionClick!(account);
|
||||
@ -220,16 +222,20 @@ const Account = ({
|
||||
<div data-testid='account' className='group block w-full shrink-0' ref={overflowRef}>
|
||||
<HStack alignItems={actionAlignment} space={3} justifyContent='between'>
|
||||
<HStack alignItems='center' space={3} className='max-w-full'>
|
||||
<div className='rounded-lg'>
|
||||
<Avatar src={account.avatar} size={avatarSize} alt={account.avatar_description} isCat={account.is_cat} />
|
||||
{emoji && (
|
||||
<Emoji
|
||||
className='!absolute -right-1.5 bottom-0 size-5'
|
||||
emoji={emoji}
|
||||
src={emojiUrl}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{disableUserProvidedMedia ? (
|
||||
<Avatar src={account.avatar} alt={account.avatar_description} />
|
||||
) : (
|
||||
<div className='rounded-lg'>
|
||||
<Avatar src={account.avatar} size={avatarSize} alt={account.avatar_description} isCat={account.is_cat} />
|
||||
{emoji && (
|
||||
<Emoji
|
||||
className='!absolute -right-1.5 bottom-0 size-5'
|
||||
emoji={emoji}
|
||||
src={emojiUrl}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='grow overflow-hidden'>
|
||||
<HStack space={1} alignItems='center' grow>
|
||||
@ -250,7 +256,7 @@ const Account = ({
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Text theme='muted' size='sm' direction='ltr' truncate>@{username}</Text>
|
||||
|
||||
{account.favicon && (
|
||||
{account.favicon && !disableUserProvidedMedia && (
|
||||
<InstanceFavicon account={account} disabled />
|
||||
)}
|
||||
|
||||
@ -271,7 +277,9 @@ const Account = ({
|
||||
<div data-testid='account' className='group block w-full shrink-0' ref={overflowRef}>
|
||||
<HStack alignItems={actionAlignment} space={3} justifyContent='between'>
|
||||
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3} className='max-w-full'>
|
||||
{withAvatar && (
|
||||
{withAvatar && (disableUserProvidedMedia ? (
|
||||
<Avatar src={account.avatar} alt={account.avatar_description} />
|
||||
) : (
|
||||
<ProfilePopper
|
||||
condition={showAccountHoverCard}
|
||||
wrapper={(children) => <HoverAccountWrapper className='relative' accountId={account.id} element='span'>{children}</HoverAccountWrapper>}
|
||||
@ -287,7 +295,7 @@ const Account = ({
|
||||
)}
|
||||
</LinkEl>
|
||||
</ProfilePopper>
|
||||
)}
|
||||
))}
|
||||
|
||||
<div className='grow overflow-hidden' style={style}>
|
||||
<ProfilePopper
|
||||
@ -315,7 +323,7 @@ const Account = ({
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Text theme='muted' size='sm' direction='ltr' truncate>@{username}</Text>
|
||||
|
||||
{account.favicon && (
|
||||
{account.favicon && !disableUserProvidedMedia && (
|
||||
<InstanceFavicon account={account} disabled={!withLinkToProfile} />
|
||||
)}
|
||||
|
||||
|
||||
@ -6,16 +6,17 @@ import Icon from 'pl-fe/components/ui/icon';
|
||||
|
||||
interface IAltIndicator extends Pick<React.HTMLAttributes<HTMLSpanElement>, 'title' | 'className'> {
|
||||
warning?: boolean;
|
||||
message?: JSX.Element;
|
||||
}
|
||||
|
||||
const AltIndicator: React.FC<IAltIndicator> = React.forwardRef<HTMLSpanElement, IAltIndicator>(({ className, warning, ...props }, ref) => (
|
||||
const AltIndicator: React.FC<IAltIndicator> = React.forwardRef<HTMLSpanElement, IAltIndicator>(({ className, warning, message, ...props }, ref) => (
|
||||
<span
|
||||
className={clsx('inline-flex items-center gap-1 rounded bg-gray-900 px-2 py-1 text-xs font-medium uppercase text-white', className)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
{warning && <Icon className='size-4' src={require('@tabler/icons/outline/alert-triangle.svg')} />}
|
||||
<FormattedMessage id='upload_form.description_missing.indicator' defaultMessage='Alt' />
|
||||
{message || <FormattedMessage id='upload_form.description_missing.indicator' defaultMessage='Alt' />}
|
||||
</span>
|
||||
));
|
||||
|
||||
|
||||
@ -17,6 +17,8 @@ import { truncateFilename } from 'pl-fe/utils/media';
|
||||
import { isIOS } from '../is-mobile';
|
||||
import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media-aspect-ratio';
|
||||
|
||||
import HStack from './ui/hstack';
|
||||
|
||||
import type { MediaAttachment } from 'pl-api';
|
||||
|
||||
const ATTACHMENT_LIMIT = 4;
|
||||
@ -315,14 +317,53 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
|
||||
visible,
|
||||
} = props;
|
||||
|
||||
const { disableUserProvidedMedia } = useSettings();
|
||||
|
||||
const [width, setWidth] = useState<number>(defaultWidth);
|
||||
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (node.current) {
|
||||
const { offsetWidth } = node.current;
|
||||
|
||||
if (cacheWidth) {
|
||||
cacheWidth(offsetWidth);
|
||||
}
|
||||
|
||||
setWidth(offsetWidth);
|
||||
}
|
||||
}, [node.current]);
|
||||
|
||||
const handleClick = (index: number) => {
|
||||
onOpenMedia(media, index);
|
||||
};
|
||||
|
||||
if (disableUserProvidedMedia) {
|
||||
return (
|
||||
<Stack space={2}>
|
||||
{media.map((attachment, index) => (
|
||||
<HStack element='button' alignItems='center' space={2} key={attachment.id} onClick={() => handleClick(index)}>
|
||||
<Icon
|
||||
className='size-4 min-w-fit text-gray-800 dark:text-gray-200'
|
||||
src={MIMETYPE_ICONS[(attachment.type === 'unknown' && attachment.mime_type) || attachment.type] || require('@tabler/icons/outline/paperclip.svg')}
|
||||
/>
|
||||
<Text align='left'>
|
||||
{attachment.description || {
|
||||
image: <FormattedMessage id='media.default_description.image' defaultMessage='Image' />,
|
||||
video: <FormattedMessage id='media.default_description.video' defaultMessage='Video' />,
|
||||
gifv: <FormattedMessage id='media.default_description.gifv' defaultMessage='GIFV' />,
|
||||
audio: <FormattedMessage id='media.default_description.audio' defaultMessage='Audio' />,
|
||||
unknown: <FormattedMessage id='media.default_description.attachment' defaultMessage='Attachment' />,
|
||||
}[attachment.type]}
|
||||
</Text>
|
||||
</HStack>
|
||||
// <MediaItem key={index} item={item} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const getSizeDataSingle = (): SizeData => {
|
||||
const w = width || defaultWidth;
|
||||
const aspectRatio = getAspectRatio(media[0]);
|
||||
@ -564,18 +605,6 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
|
||||
/>
|
||||
));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (node.current) {
|
||||
const { offsetWidth } = node.current;
|
||||
|
||||
if (cacheWidth) {
|
||||
cacheWidth(offsetWidth);
|
||||
}
|
||||
|
||||
setWidth(offsetWidth);
|
||||
}
|
||||
}, [node.current]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(className, 'media-gallery overflow-hidden rounded-md', {
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
import clsx from 'clsx';
|
||||
import { FastAverageColor } from 'fast-average-color';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import StillImage, { IStillImage } from 'pl-fe/components/still-image';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
|
||||
import AltIndicator from '../alt-indicator';
|
||||
|
||||
import Icon from './icon';
|
||||
import Popover from './popover';
|
||||
import Stack from './stack';
|
||||
import Text from './text';
|
||||
|
||||
const COLOR_CACHE = new Map<string, string>();
|
||||
|
||||
@ -27,6 +33,7 @@ const fac = new FastAverageColor();
|
||||
/** Round profile avatar for accounts. */
|
||||
const Avatar = (props: IAvatar) => {
|
||||
const intl = useIntl();
|
||||
const { disableUserProvidedMedia } = useSettings();
|
||||
|
||||
const { alt, src, size = AVATAR_SIZE, className, isCat } = props;
|
||||
|
||||
@ -58,6 +65,29 @@ const Avatar = (props: IAvatar) => {
|
||||
color,
|
||||
}), [size, color]);
|
||||
|
||||
if (disableUserProvidedMedia) {
|
||||
if (isAvatarMissing || !alt) return null;
|
||||
return (
|
||||
<Popover
|
||||
interaction='hover'
|
||||
referenceElementClassName='cursor-pointer'
|
||||
content={
|
||||
<Stack space={1} className='max-h-[32rem] max-w-96 overflow-auto p-4'>
|
||||
<Text weight='semibold'>
|
||||
<FormattedMessage id='account.avatar.description' defaultMessage='Avatar description' />
|
||||
</Text>
|
||||
<Text className='whitespace-pre-wrap'>
|
||||
{alt}
|
||||
</Text>
|
||||
</Stack>
|
||||
}
|
||||
isFlush
|
||||
>
|
||||
<AltIndicator message={<FormattedMessage id='account.avatar.alt' defaultMessage='Avatar' />} />
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
if (isAvatarMissing) {
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import StillImage from 'pl-fe/components/still-image';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
import { removeVS16s, toCodePoints } from 'pl-fe/utils/emoji';
|
||||
import { joinPublicPath } from 'pl-fe/utils/static';
|
||||
|
||||
@ -12,6 +13,7 @@ interface IEmoji extends Pick<React.ImgHTMLAttributes<HTMLImageElement>, 'alt' |
|
||||
|
||||
/** A single emoji image. */
|
||||
const Emoji: React.FC<IEmoji> = (props): JSX.Element | null => {
|
||||
const { disableUserProvidedMedia } = useSettings();
|
||||
const { emoji, alt, src, noGroup, ...rest } = props;
|
||||
|
||||
let filename;
|
||||
@ -24,6 +26,7 @@ const Emoji: React.FC<IEmoji> = (props): JSX.Element | null => {
|
||||
if (!filename && !src) return null;
|
||||
|
||||
if (src) {
|
||||
if (disableUserProvidedMedia) return <>{alt || emoji}</>;
|
||||
return (
|
||||
<StillImage
|
||||
alt={alt || emoji}
|
||||
@ -40,7 +43,7 @@ const Emoji: React.FC<IEmoji> = (props): JSX.Element | null => {
|
||||
<img
|
||||
draggable='false'
|
||||
alt={alt || emoji}
|
||||
src={src || joinPublicPath(`packs/emoji/${filename}.svg`)}
|
||||
src={joinPublicPath(`packs/emoji/${filename}.svg`)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -3,9 +3,12 @@ import fileCodeIcon from '@tabler/icons/outline/file-code.svg';
|
||||
import fileSpreadsheetIcon from '@tabler/icons/outline/file-spreadsheet.svg';
|
||||
import fileTextIcon from '@tabler/icons/outline/file-text.svg';
|
||||
import fileZipIcon from '@tabler/icons/outline/file-zip.svg';
|
||||
import audioIcon from '@tabler/icons/outline/music.svg';
|
||||
import defaultIcon from '@tabler/icons/outline/paperclip.svg';
|
||||
import editIcon from '@tabler/icons/outline/pencil.svg';
|
||||
import imageIcon from '@tabler/icons/outline/photo.svg';
|
||||
import presentationIcon from '@tabler/icons/outline/presentation.svg';
|
||||
import videoIcon from '@tabler/icons/outline/video.svg';
|
||||
import xIcon from '@tabler/icons/outline/x.svg';
|
||||
import zoomInIcon from '@tabler/icons/outline/zoom-in.svg';
|
||||
import clsx from 'clsx';
|
||||
@ -55,6 +58,10 @@ const MIMETYPE_ICONS: Record<string, string> = {
|
||||
'application/x-abiword': fileTextIcon,
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': fileTextIcon,
|
||||
'application/vnd.oasis.opendocument.text': fileTextIcon,
|
||||
image: imageIcon,
|
||||
video: videoIcon,
|
||||
gifv: videoIcon,
|
||||
audio: audioIcon,
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import clsx from 'clsx';
|
||||
import { GOTOSOCIAL, MASTODON, mediaAttachmentSchema } from 'pl-api';
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
@ -9,12 +10,16 @@ import { biteAccount, blockAccount, pinAccount, removeFromFollowers, unblockAcco
|
||||
import { mentionCompose, directCompose } from 'pl-fe/actions/compose';
|
||||
import { initReport, ReportableEntities } from 'pl-fe/actions/reports';
|
||||
import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow';
|
||||
import AltIndicator from 'pl-fe/components/alt-indicator';
|
||||
import Badge from 'pl-fe/components/badge';
|
||||
import DropdownMenu, { Menu } from 'pl-fe/components/dropdown-menu';
|
||||
import StillImage from 'pl-fe/components/still-image';
|
||||
import Avatar from 'pl-fe/components/ui/avatar';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import IconButton from 'pl-fe/components/ui/icon-button';
|
||||
import Popover from 'pl-fe/components/ui/popover';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import VerificationBadge from 'pl-fe/components/verification-badge';
|
||||
import MovedNote from 'pl-fe/features/account-timeline/components/moved-note';
|
||||
import ActionButton from 'pl-fe/features/ui/components/action-button';
|
||||
@ -23,11 +28,11 @@ import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||
import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
import { useChats } from 'pl-fe/queries/chats';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { blockDomainMutationOptions, unblockDomainMutationOptions } from 'pl-fe/queries/settings/domain-blocks';
|
||||
import { useModalsStore } from 'pl-fe/stores/modals';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import toast from 'pl-fe/toast';
|
||||
import { isDefaultHeader } from 'pl-fe/utils/accounts';
|
||||
import copy from 'pl-fe/utils/copy';
|
||||
@ -98,7 +103,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||
const { account: ownAccount } = useOwnAccount();
|
||||
const { follow } = useFollow();
|
||||
const { openModal } = useModalsStore();
|
||||
const { settings } = useSettingsStore();
|
||||
const settings = useSettings();
|
||||
|
||||
const { software } = features.version;
|
||||
|
||||
@ -566,6 +571,29 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||
const renderHeader = () => {
|
||||
let header: React.ReactNode;
|
||||
|
||||
if (settings.disableUserProvidedMedia) {
|
||||
if (!account.header_description) return null;
|
||||
else return (
|
||||
<Popover
|
||||
interaction='hover'
|
||||
referenceElementClassName='cursor-pointer'
|
||||
content={
|
||||
<Stack space={1} className='max-h-[32rem] max-w-96 overflow-auto p-4'>
|
||||
<Text weight='semibold'>
|
||||
<FormattedMessage id='account.header.description' defaultMessage='Header description' />
|
||||
</Text>
|
||||
<Text className='whitespace-pre-wrap'>
|
||||
{account.header_description}
|
||||
</Text>
|
||||
</Stack>
|
||||
}
|
||||
isFlush
|
||||
>
|
||||
<AltIndicator className='ml-6 mt-6 w-fit' message={<FormattedMessage id='account.header.alt' defaultMessage='Header' />} />
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
if (account.header) {
|
||||
header = (
|
||||
<StillImage
|
||||
@ -655,7 +683,11 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 black:rounded-t-none dark:bg-gray-900/50 md:rounded-t-xl lg:h-48'>
|
||||
<div
|
||||
className={clsx('relative isolate flex w-full flex-col justify-center overflow-hidden black:rounded-t-none md:rounded-t-xl', {
|
||||
'h-32 bg-gray-200 dark:bg-gray-900/50 lg:h-48': !settings.disableUserProvidedMedia,
|
||||
})}
|
||||
>
|
||||
{renderHeader()}
|
||||
|
||||
<div className='absolute left-2 top-2'>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import split from 'graphemesplit';
|
||||
import React from 'react';
|
||||
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
import { makeEmojiMap } from 'pl-fe/utils/normalizers';
|
||||
|
||||
import unicodeMapping from './mapping';
|
||||
@ -34,6 +35,8 @@ interface IEmojify {
|
||||
}
|
||||
|
||||
const Emojify: React.FC<IEmojify> = React.memo(({ text, emojis = {} }) => {
|
||||
const { disableUserProvidedMedia } = useSettings();
|
||||
|
||||
if (Array.isArray(emojis)) emojis = makeEmojiMap(emojis);
|
||||
|
||||
const nodes = [];
|
||||
@ -76,7 +79,7 @@ const Emojify: React.FC<IEmojify> = React.memo(({ text, emojis = {} }) => {
|
||||
nodes.push(
|
||||
<img key={index} draggable={false} className='emojione transition-transform hover:scale-125' alt={unqualified} title={`:${shortcode}:`} src={`/packs/emoji/${unified}.svg`} />,
|
||||
);
|
||||
} else if (c === ':') {
|
||||
} else if (!disableUserProvidedMedia && c === ':') {
|
||||
if (!open) {
|
||||
clearStack();
|
||||
}
|
||||
|
||||
@ -282,6 +282,13 @@ const Preferences = () => {
|
||||
<SettingToggle settings={settings} settingPath={['checkEmojiReactsSupport']} onChange={onToggleChange} />
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
<ListItem
|
||||
label={<FormattedMessage id='preferences.fields.disable_user_provided_media_label' defaultMessage='Do not display user-provided media' />}
|
||||
hint={<FormattedMessage id='preferences.fields.disable_user_provided_media_hint' defaultMessage='This will hide images, videos, and other media uploaded by users and display alternative text instead.' />}
|
||||
>
|
||||
<SettingToggle settings={settings} settingPath={['disableUserProvidedMedia']} onChange={onToggleChange} />
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<List>
|
||||
|
||||
@ -25,7 +25,7 @@ interface IUserPanel {
|
||||
|
||||
const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain }) => {
|
||||
const intl = useIntl();
|
||||
const { demetricator } = useSettings();
|
||||
const { demetricator, disableUserProvidedMedia } = useSettings();
|
||||
const { account } = useAccount(accountId);
|
||||
const fqn = useAppSelector((state) => displayFqn(state));
|
||||
|
||||
@ -38,26 +38,30 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
|
||||
<div className='relative'>
|
||||
<Stack space={2}>
|
||||
<Stack>
|
||||
<div className='relative -mx-4 -mt-4 h-24 overflow-hidden bg-gray-200'>
|
||||
{header && (
|
||||
<StillImage src={account.header} alt={account.header_description} />
|
||||
)}
|
||||
</div>
|
||||
{!disableUserProvidedMedia && (
|
||||
<div className='relative -mx-4 -mt-4 h-24 overflow-hidden bg-gray-200'>
|
||||
{header && (
|
||||
<StillImage src={account.header} alt={account.header_description} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<HStack justifyContent='between'>
|
||||
<Link
|
||||
to={`/@${account.acct}`}
|
||||
title={acct}
|
||||
className='-mt-12 block'
|
||||
>
|
||||
<Avatar
|
||||
src={account.avatar}
|
||||
alt={account.avatar_description}
|
||||
isCat={account.is_cat}
|
||||
size={80}
|
||||
className='size-20 bg-gray-50 ring-2 ring-white'
|
||||
/>
|
||||
</Link>
|
||||
<HStack justifyContent={disableUserProvidedMedia ? 'end' : 'between'}>
|
||||
{!disableUserProvidedMedia && (
|
||||
<Link
|
||||
to={`/@${account.acct}`}
|
||||
title={acct}
|
||||
className='-mt-12 block'
|
||||
>
|
||||
<Avatar
|
||||
src={account.avatar}
|
||||
alt={account.avatar_description}
|
||||
isCat={account.is_cat}
|
||||
size={80}
|
||||
className='size-20 bg-gray-50 ring-2 ring-white'
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{action && (
|
||||
<div className='mt-2'>{action}</div>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"accordion.expand": "Expand",
|
||||
"account.add_or_remove_from_list": "Add or Remove from lists",
|
||||
"account.avatar.alt": "Avatar",
|
||||
"account.avatar.description": "Avatar description",
|
||||
"account.badges.bot": "Bot",
|
||||
"account.birthday": "Born {date}",
|
||||
"account.birthday_today": "Birthday is today!",
|
||||
@ -31,6 +32,7 @@
|
||||
"account.follows.empty": "This user doesn't follow anyone yet.",
|
||||
"account.follows_you": "Follows you",
|
||||
"account.header.alt": "Profile header",
|
||||
"account.header.description": "Header description",
|
||||
"account.hide_reblogs": "Hide reposts from @{name}",
|
||||
"account.last_status": "Last active",
|
||||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
@ -657,12 +659,14 @@
|
||||
"edit_federation.success": "{host} federation was updated",
|
||||
"edit_federation.unlisted": "Force posts unlisted",
|
||||
"edit_password.header": "Change password",
|
||||
"edit_profile.custom_css.remaining_characters": "{remaining, plural, one {# character} other {# characters}} remaining",
|
||||
"edit_profile.error": "Profile update failed",
|
||||
"edit_profile.fields.bio_label": "Bio",
|
||||
"edit_profile.fields.bio_placeholder": "Tell us about yourself.",
|
||||
"edit_profile.fields.birthday_label": "Birthday",
|
||||
"edit_profile.fields.birthday_placeholder": "Your birthday",
|
||||
"edit_profile.fields.bot_label": "This is a bot account",
|
||||
"edit_profile.fields.custom_css_label": "Custom CSS",
|
||||
"edit_profile.fields.discoverable_label": "Allow account discovery",
|
||||
"edit_profile.fields.display_name_label": "Display name",
|
||||
"edit_profile.fields.display_name_placeholder": "Name",
|
||||
@ -681,6 +685,11 @@
|
||||
"edit_profile.fields.rss_label": "Enable RSS feed for public posts",
|
||||
"edit_profile.fields.speak_as_cat_label": "The user speaks as a cat",
|
||||
"edit_profile.fields.stranger_notifications_label": "Block notifications from strangers",
|
||||
"edit_profile.fields.web_layout.gallery": "Media-only gallery layout",
|
||||
"edit_profile.fields.web_layout.microblog": "Classic microblog layout",
|
||||
"edit_profile.fields.web_visibility.none": "Show no posts",
|
||||
"edit_profile.fields.web_visibility.public": "Show public posts only",
|
||||
"edit_profile.fields.web_visibility.unlisted": "Show public and unlisted posts",
|
||||
"edit_profile.header": "Edit profile",
|
||||
"edit_profile.hints.bot": "This account mainly performs automated actions and might not be monitored",
|
||||
"edit_profile.hints.discoverable": "Display account in profile directory and allow indexing by external services",
|
||||
@ -1068,6 +1077,11 @@
|
||||
"manage_group.fields.name_placeholder": "Group Name",
|
||||
"manage_group.pending_requests": "Pending requests",
|
||||
"media-gallery.description": "Image description",
|
||||
"media.default_description.attachment": "Attachment",
|
||||
"media.default_description.audio": "Audio",
|
||||
"media.default_description.gifv": "GIFV",
|
||||
"media.default_description.image": "Image",
|
||||
"media.default_description.video": "Video",
|
||||
"media_panel.empty_message": "No media found.",
|
||||
"media_panel.title": "Media",
|
||||
"mfa.confirm.success_message": "MFA confirmed",
|
||||
@ -1292,6 +1306,8 @@
|
||||
"preferences.fields.demetricator_label": "Hide social media counters",
|
||||
"preferences.fields.demo_hint": "Use the default pl-fe logo and color scheme. Useful for taking screenshots.",
|
||||
"preferences.fields.demo_label": "Demo mode",
|
||||
"preferences.fields.disable_user_provided_media_hint": "This will hide images, videos, and other media uploaded by users and display alternative text instead.",
|
||||
"preferences.fields.disable_user_provided_media_label": "Do not display user-provided media",
|
||||
"preferences.fields.display_media.default": "Hide posts marked as sensitive",
|
||||
"preferences.fields.display_media.hide_all": "Always hide media posts",
|
||||
"preferences.fields.display_media.show_all": "Always show posts",
|
||||
@ -1310,6 +1326,8 @@
|
||||
"preferences.fields.theme_reset": "Reset theme",
|
||||
"preferences.fields.underline_links_label": "Always underline links in posts",
|
||||
"preferences.fields.unfollow_modal_label": "Show confirmation dialog before unfollowing someone",
|
||||
"preferences.fields.web_layout_label": "Layout of the web view of your profile",
|
||||
"preferences.fields.web_visibility_label": "Visibility level of posts displayed on your profile",
|
||||
"preferences.fields.wrench_label": "Display wrench reaction button",
|
||||
"preferences.hints.demetricator": "Decrease social media anxiety by hiding all numbers from the site.",
|
||||
"preferences.hints.mention_policy": "Applies to direct messages and public posts",
|
||||
|
||||
@ -49,6 +49,7 @@ const settingsSchema = v.object({
|
||||
redirectServices: v.fallback(v.record(v.string(), v.string()), {}),
|
||||
}),
|
||||
checkEmojiReactsSupport: v.fallback(v.boolean(), false),
|
||||
disableUserProvidedMedia: v.fallback(v.boolean(), false),
|
||||
|
||||
theme: v.fallback(v.optional(v.object({
|
||||
brandColor: v.fallback(v.string(), ''),
|
||||
|
||||
Reference in New Issue
Block a user