Merge remote-tracking branch 'origin/develop' into react-any-emoji
This commit is contained in:
@@ -4,9 +4,8 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { expandUserIndex, fetchUserIndex, setUserIndexQuery } from 'soapbox/actions/admin';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Column } from 'soapbox/components/ui';
|
||||
import { Column, Input } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import { SimpleForm, TextInput } from 'soapbox/features/forms';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -22,7 +21,7 @@ const UserIndex: React.FC = () => {
|
||||
const { isLoading, items, total, query, next } = useAppSelector((state) => state.admin_user_index);
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(expandUserIndex());
|
||||
if (!isLoading) dispatch(expandUserIndex());
|
||||
};
|
||||
|
||||
const updateQuery = useCallback(debounce(() => {
|
||||
@@ -31,25 +30,25 @@ const UserIndex: React.FC = () => {
|
||||
|
||||
const handleQueryChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||
dispatch(setUserIndexQuery(e.target.value));
|
||||
updateQuery();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateQuery();
|
||||
}, [query]);
|
||||
}, []);
|
||||
|
||||
const hasMore = items.count() < total && next !== null;
|
||||
|
||||
const hasMore = items.count() < total && !!next;
|
||||
|
||||
const showLoading = isLoading && items.isEmpty();
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<SimpleForm style={{ paddingBottom: 0 }}>
|
||||
<TextInput
|
||||
value={query}
|
||||
onChange={handleQueryChange}
|
||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||
/>
|
||||
</SimpleForm>
|
||||
<Input
|
||||
value={query}
|
||||
onChange={handleQueryChange}
|
||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||
/>
|
||||
<ScrollableList
|
||||
scrollKey='user-index'
|
||||
hasMore={hasMore}
|
||||
|
||||
@@ -581,4 +581,4 @@ const Audio: React.FC<IAudio> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Audio;
|
||||
export default Audio;
|
||||
|
||||
@@ -42,6 +42,7 @@ interface IChatComposer extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaEl
|
||||
errorMessage: string | undefined
|
||||
onSelectFile: (files: FileList, intl: IntlShape) => void
|
||||
resetFileKey: number | null
|
||||
resetContentKey: number | null
|
||||
attachments?: Attachment[]
|
||||
onDeleteAttachment?: () => void
|
||||
isUploading?: boolean
|
||||
@@ -58,6 +59,7 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
||||
disabled = false,
|
||||
onSelectFile,
|
||||
resetFileKey,
|
||||
resetContentKey,
|
||||
onPaste,
|
||||
attachments = [],
|
||||
onDeleteAttachment,
|
||||
@@ -179,6 +181,7 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
||||
<Stack grow>
|
||||
<Combobox onSelect={onSelectComboboxOption}>
|
||||
<ComboboxInput
|
||||
key={resetContentKey}
|
||||
as={ChatTextarea}
|
||||
autoFocus
|
||||
ref={ref}
|
||||
@@ -250,4 +253,4 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
||||
);
|
||||
});
|
||||
|
||||
export default ChatComposer;
|
||||
export default ChatComposer;
|
||||
|
||||
@@ -54,6 +54,7 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
const [attachment, setAttachment] = useState<any>(undefined);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [resetContentKey, setResetContentKey] = useState<number>(fileKeyGen());
|
||||
const [resetFileKey, setResetFileKey] = useState<number>(fileKeyGen());
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
@@ -83,6 +84,7 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
setIsUploading(false);
|
||||
setUploadProgress(0);
|
||||
setResetFileKey(fileKeyGen());
|
||||
setResetContentKey(fileKeyGen());
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
@@ -171,6 +173,7 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
errorMessage={errorMessage}
|
||||
onSelectFile={handleFiles}
|
||||
resetFileKey={resetFileKey}
|
||||
resetContentKey={resetContentKey}
|
||||
onPaste={handlePaste}
|
||||
attachments={attachment ? [attachment] : []}
|
||||
onDeleteAttachment={handleRemoveFile}
|
||||
|
||||
@@ -242,7 +242,10 @@ const PrivacyDropdown: React.FC<IPrivacyDropdown> = ({
|
||||
<div className={clsx('privacy-dropdown', placement, { active: open })} onKeyDown={handleKeyDown} ref={node}>
|
||||
<div className={clsx('privacy-dropdown__value', { active: valueOption && options.indexOf(valueOption) === 0 })}>
|
||||
<IconButton
|
||||
className='text-gray-600 hover:text-gray-700 dark:hover:text-gray-500'
|
||||
className={clsx({
|
||||
'text-gray-600 hover:text-gray-700 dark:hover:text-gray-500': !open,
|
||||
'text-primary-500 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-400': open,
|
||||
})}
|
||||
src={valueOption?.icon}
|
||||
title={intl.formatMessage(messages.change_privacy)}
|
||||
onClick={handleToggle}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
|
||||
@@ -8,12 +9,13 @@ import { isRtl } from 'soapbox/rtl';
|
||||
import type { Status } from 'soapbox/types/entities';
|
||||
|
||||
interface IReplyIndicator {
|
||||
className?: string,
|
||||
status?: Status,
|
||||
onCancel?: () => void,
|
||||
hideActions: boolean,
|
||||
}
|
||||
|
||||
const ReplyIndicator: React.FC<IReplyIndicator> = ({ status, hideActions, onCancel }) => {
|
||||
const ReplyIndicator: React.FC<IReplyIndicator> = ({ className, status, hideActions, onCancel }) => {
|
||||
const handleClick = () => {
|
||||
onCancel!();
|
||||
};
|
||||
@@ -33,7 +35,7 @@ const ReplyIndicator: React.FC<IReplyIndicator> = ({ status, hideActions, onCanc
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack space={2} className='rounded-lg bg-gray-100 p-4 dark:bg-gray-800'>
|
||||
<Stack space={2} className={clsx('rounded-lg bg-gray-100 p-4 dark:bg-gray-800', className)}>
|
||||
<AccountContainer
|
||||
{...actions}
|
||||
id={status.getIn(['account', 'id']) as string}
|
||||
|
||||
@@ -21,4 +21,4 @@ const Indicator: React.FC<IIndicator> = ({ state = 'inactive', size = 'sm' }) =>
|
||||
);
|
||||
};
|
||||
|
||||
export default Indicator;
|
||||
export default Indicator;
|
||||
|
||||
@@ -152,14 +152,14 @@ const ProfileField: StreamfieldComponent<AccountCredentialsField> = ({ value, on
|
||||
<HStack space={2} grow>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-2/5 flex-grow'
|
||||
outerClassName='w-2/5 grow'
|
||||
value={value.name}
|
||||
onChange={handleChange('name')}
|
||||
placeholder={intl.formatMessage(messages.metaFieldLabel)}
|
||||
/>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-3/5 flex-grow'
|
||||
outerClassName='w-3/5 grow'
|
||||
value={value.value}
|
||||
onChange={handleChange('value')}
|
||||
placeholder={intl.formatMessage(messages.metaFieldContent)}
|
||||
|
||||
@@ -2,13 +2,9 @@ import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchFilters, createFilter, deleteFilter } from 'soapbox/actions/filters';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Button, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
import {
|
||||
FieldsGroup,
|
||||
Checkbox,
|
||||
} from 'soapbox/features/forms';
|
||||
import { Button, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, HStack, IconButton, Input, Stack, Text, Toggle } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
@@ -33,6 +29,13 @@ const messages = defineMessages({
|
||||
delete: { id: 'column.filters.delete', defaultMessage: 'Delete' },
|
||||
});
|
||||
|
||||
const contexts = {
|
||||
home: messages.home_timeline,
|
||||
public: messages.public_timeline,
|
||||
notifications: messages.notifications,
|
||||
thread: messages.conversations,
|
||||
};
|
||||
|
||||
// const expirations = {
|
||||
// null: 'Never',
|
||||
// // 3600: '30 minutes',
|
||||
@@ -85,8 +88,8 @@ const Filters = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleFilterDelete: React.MouseEventHandler<HTMLDivElement> = e => {
|
||||
dispatch(deleteFilter(e.currentTarget.dataset.value!)).then(() => {
|
||||
const handleFilterDelete = (id: string) => () => {
|
||||
dispatch(deleteFilter(id)).then(() => {
|
||||
return dispatch(fetchFilters());
|
||||
}).catch(() => {
|
||||
toast.error(intl.formatMessage(messages.delete_error));
|
||||
@@ -121,58 +124,68 @@ const Filters = () => {
|
||||
/>
|
||||
</FormGroup> */}
|
||||
|
||||
<FieldsGroup>
|
||||
<Text tag='label'>
|
||||
<Stack>
|
||||
<Text size='sm' weight='medium'>
|
||||
<FormattedMessage id='filters.context_header' defaultMessage='Filter contexts' />
|
||||
</Text>
|
||||
<Text theme='muted' size='xs'>
|
||||
<Text size='xs' theme='muted'>
|
||||
<FormattedMessage id='filters.context_hint' defaultMessage='One or multiple contexts where the filter should apply' />
|
||||
</Text>
|
||||
<div className='two-col'>
|
||||
<Checkbox
|
||||
label={intl.formatMessage(messages.home_timeline)}
|
||||
</Stack>
|
||||
|
||||
<List>
|
||||
<ListItem label={intl.formatMessage(messages.home_timeline)}>
|
||||
<Toggle
|
||||
name='home_timeline'
|
||||
checked={homeTimeline}
|
||||
onChange={({ target }) => setHomeTimeline(target.checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={intl.formatMessage(messages.public_timeline)}
|
||||
</ListItem>
|
||||
<ListItem label={intl.formatMessage(messages.public_timeline)}>
|
||||
<Toggle
|
||||
name='public_timeline'
|
||||
checked={publicTimeline}
|
||||
onChange={({ target }) => setPublicTimeline(target.checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={intl.formatMessage(messages.notifications)}
|
||||
</ListItem>
|
||||
<ListItem label={intl.formatMessage(messages.notifications)}>
|
||||
<Toggle
|
||||
name='notifications'
|
||||
checked={notifications}
|
||||
onChange={({ target }) => setNotifications(target.checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={intl.formatMessage(messages.conversations)}
|
||||
</ListItem>
|
||||
<ListItem label={intl.formatMessage(messages.conversations)}>
|
||||
<Toggle
|
||||
name='conversations'
|
||||
checked={conversations}
|
||||
onChange={({ target }) => setConversations(target.checked)}
|
||||
/>
|
||||
</div>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
</FieldsGroup>
|
||||
|
||||
<FieldsGroup>
|
||||
<Checkbox
|
||||
<List>
|
||||
<ListItem
|
||||
label={intl.formatMessage(messages.drop_header)}
|
||||
hint={intl.formatMessage(messages.drop_hint)}
|
||||
name='irreversible'
|
||||
checked={irreversible}
|
||||
onChange={({ target }) => setIrreversible(target.checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
>
|
||||
<Toggle
|
||||
name='irreversible'
|
||||
checked={irreversible}
|
||||
onChange={({ target }) => setIrreversible(target.checked)}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
label={intl.formatMessage(messages.whole_word_header)}
|
||||
hint={intl.formatMessage(messages.whole_word_hint)}
|
||||
name='whole_word'
|
||||
checked={wholeWord}
|
||||
onChange={({ target }) => setWholeWord(target.checked)}
|
||||
/>
|
||||
</FieldsGroup>
|
||||
>
|
||||
<Toggle
|
||||
name='whole_word'
|
||||
checked={wholeWord}
|
||||
onChange={({ target }) => setWholeWord(target.checked)}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<FormActions>
|
||||
<Button type='submit' theme='primary'>{intl.formatMessage(messages.add_new)}</Button>
|
||||
@@ -186,40 +199,41 @@ const Filters = () => {
|
||||
<ScrollableList
|
||||
scrollKey='filters'
|
||||
emptyMessage={emptyMessage}
|
||||
itemClassName='pb-4 last:pb-0'
|
||||
>
|
||||
{filters.map((filter, i) => (
|
||||
<div key={i} className='filter__container'>
|
||||
<div className='filter__details'>
|
||||
<div className='filter__phrase'>
|
||||
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_phrase_label' defaultMessage='Keyword or phrase:' /></span>
|
||||
<span className='filter__list-value'>{filter.phrase}</span>
|
||||
</div>
|
||||
<div className='filter__contexts'>
|
||||
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' /></span>
|
||||
<span className='filter__list-value'>
|
||||
{filter.context.map((context, i) => (
|
||||
<span key={i} className='context'>{context}</span>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className='filter__details'>
|
||||
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_details_label' defaultMessage='Filter settings:' /></span>
|
||||
<span className='filter__list-value'>
|
||||
<HStack space={1} justifyContent='between'>
|
||||
<Stack space={1}>
|
||||
<Text weight='medium'>
|
||||
<FormattedMessage id='filters.filters_list_phrase_label' defaultMessage='Keyword or phrase:' />
|
||||
{' '}
|
||||
<Text theme='muted' tag='span'>{filter.phrase}</Text>
|
||||
</Text>
|
||||
<Text weight='medium'>
|
||||
<FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' />
|
||||
{' '}
|
||||
<Text theme='muted' tag='span'>{filter.context.map(context => contexts[context] ? intl.formatMessage(contexts[context]) : context).join(', ')}</Text>
|
||||
</Text>
|
||||
<HStack space={4}>
|
||||
<Text weight='medium'>
|
||||
{filter.irreversible ?
|
||||
<span><FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /></span> :
|
||||
<span><FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' /></span>
|
||||
}
|
||||
{filter.whole_word &&
|
||||
<span><FormattedMessage id='filters.filters_list_whole-word' defaultMessage='Whole word' /></span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='filter__delete' role='button' tabIndex={0} onClick={handleFilterDelete} data-value={filter.id} aria-label={intl.formatMessage(messages.delete)}>
|
||||
<Icon className='filter__delete-icon' src={require('@tabler/icons/x.svg')} />
|
||||
<span className='filter__delete-label'><FormattedMessage id='filters.filters_list_delete' defaultMessage='Delete' /></span>
|
||||
</div>
|
||||
</div>
|
||||
<FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /> :
|
||||
<FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' />}
|
||||
</Text>
|
||||
{filter.whole_word && (
|
||||
<Text weight='medium'>
|
||||
<FormattedMessage id='filters.filters_list_whole-word' defaultMessage='Whole word' />
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
</Stack>
|
||||
<IconButton
|
||||
iconClassName='h-5 w-5 text-gray-700 dark:text-gray-600 hover:text-gray-800 dark:hover:text-gray-500'
|
||||
src={require('@tabler/icons/trash.svg')}
|
||||
onClick={handleFilterDelete(filter.id)}
|
||||
title={intl.formatMessage(messages.delete)}
|
||||
/>
|
||||
</HStack>
|
||||
))}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
|
||||
@@ -103,71 +103,6 @@ export const SimpleInput: React.FC<ISimpleInput> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface ISimpleTextarea {
|
||||
label?: React.ReactNode,
|
||||
hint?: React.ReactNode,
|
||||
value?: string,
|
||||
onChange?: React.ChangeEventHandler<HTMLTextAreaElement>,
|
||||
rows?: number,
|
||||
name?: string,
|
||||
maxLength?: number,
|
||||
required?: boolean,
|
||||
}
|
||||
|
||||
export const SimpleTextarea: React.FC<ISimpleTextarea> = (props) => {
|
||||
const { hint, label, ...rest } = props;
|
||||
const Input = label ? LabelTextarea : 'textarea';
|
||||
|
||||
return (
|
||||
<InputContainer {...props}>
|
||||
<Input {...rest} />
|
||||
</InputContainer>
|
||||
);
|
||||
};
|
||||
|
||||
interface ISimpleForm {
|
||||
className?: string,
|
||||
onSubmit?: React.FormEventHandler,
|
||||
acceptCharset?: string,
|
||||
style?: React.CSSProperties,
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
export const SimpleForm: React.FC<ISimpleForm> = (props) => {
|
||||
const {
|
||||
className,
|
||||
children,
|
||||
onSubmit = () => {},
|
||||
acceptCharset = 'UTF-8',
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const handleSubmit: React.FormEventHandler = e => {
|
||||
onSubmit(e);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
className={clsx('simple_form', className)}
|
||||
method='post'
|
||||
onSubmit={handleSubmit}
|
||||
acceptCharset={acceptCharset}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
interface IFieldsGroup {
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
export const FieldsGroup: React.FC<IFieldsGroup> = ({ children }) => (
|
||||
<div className='fields-group'>{children}</div>
|
||||
);
|
||||
|
||||
interface ICheckbox {
|
||||
label?: React.ReactNode,
|
||||
hint?: React.ReactNode,
|
||||
|
||||
@@ -36,7 +36,7 @@ const ListForm = () => {
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<HStack space={2}>
|
||||
<Input
|
||||
outerClassName='flex-grow'
|
||||
outerClassName='grow'
|
||||
type='text'
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
|
||||
interface IClearColumnButton {
|
||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
const ClearColumnButton: React.FC<IClearColumnButton> = ({ onClick }) => (
|
||||
<button className='text-btn column-header__setting-btn' tabIndex={0} onClick={onClick}>
|
||||
<Icon src={require('@tabler/icons/eraser.svg')} />
|
||||
{' '}
|
||||
<FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' />
|
||||
</button>
|
||||
);
|
||||
|
||||
export default ClearColumnButton;
|
||||
@@ -329,6 +329,7 @@ const Notification: React.FC<INotificaton> = (props) => {
|
||||
onMoveDown={handleMoveDown}
|
||||
onMoveUp={handleMoveUp}
|
||||
avatarSize={avatarSize}
|
||||
contextType='notifications'
|
||||
/>
|
||||
) : null;
|
||||
default:
|
||||
|
||||
@@ -78,7 +78,7 @@ const PlaceholderMediaGallery: React.FC<IPlaceholderMediaGallery> = ({ media, de
|
||||
const float = dimensions.float as any || 'left';
|
||||
const position = dimensions.pos as any || 'relative';
|
||||
|
||||
return <div key={i} className='media-gallery__item' style={{ position, float, left, top, right, bottom, height, width }} />;
|
||||
return <div key={i} className='media-gallery__item animate-pulse bg-primary-200' style={{ position, float, left, top, right, bottom, height, width }} />;
|
||||
};
|
||||
|
||||
const sizeData = getSizeData(media.size);
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { getSettings, changeSettingImmediate } from 'soapbox/actions/settings';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import { Card, CardBody, CardHeader, CardTitle } from 'soapbox/components/ui';
|
||||
import { SimpleForm, SelectDropdown } from 'soapbox/features/forms';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
mediaDisplay: { id: 'preferences.fields.media_display_label', defaultMessage: 'Media display' },
|
||||
display_media_default: { id: 'preferences.fields.display_media.default', defaultMessage: 'Hide media marked as sensitive' },
|
||||
display_media_hide_all: { id: 'preferences.fields.display_media.hide_all', defaultMessage: 'Always hide media' },
|
||||
display_media_show_all: { id: 'preferences.fields.display_media.show_all', defaultMessage: 'Always show media' },
|
||||
});
|
||||
|
||||
const MediaDisplay = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const settings = useAppSelector((state) => getSettings(state));
|
||||
|
||||
const displayMediaOptions = {
|
||||
default: intl.formatMessage(messages.display_media_default),
|
||||
hide_all: intl.formatMessage(messages.display_media_hide_all),
|
||||
show_all: intl.formatMessage(messages.display_media_show_all),
|
||||
};
|
||||
|
||||
const onSelectChange: (path: string[]) => React.ChangeEventHandler<HTMLSelectElement> = path => {
|
||||
return e => {
|
||||
dispatch(changeSettingImmediate(path, e.target.value));
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<Card variant='rounded'>
|
||||
<CardHeader>
|
||||
<CardTitle title={intl.formatMessage(messages.mediaDisplay)} />
|
||||
</CardHeader>
|
||||
|
||||
<CardBody>
|
||||
<SimpleForm className='space-y-3 p-0'>
|
||||
<List>
|
||||
<ListItem label={intl.formatMessage(messages.mediaDisplay)}>
|
||||
<SelectDropdown
|
||||
items={displayMediaOptions}
|
||||
defaultValue={settings.get('displayMedia') as string}
|
||||
onChange={onSelectChange(['displayMedia'])}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</SimpleForm>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default MediaDisplay;
|
||||
@@ -25,21 +25,21 @@ const CryptoAddressInput: StreamfieldComponent<CryptoAddress> = ({ value, onChan
|
||||
<HStack space={2} grow>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-1/6 flex-grow'
|
||||
outerClassName='w-1/6 grow'
|
||||
value={value.ticker}
|
||||
onChange={handleChange('ticker')}
|
||||
placeholder={intl.formatMessage(messages.ticker)}
|
||||
/>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-3/6 flex-grow'
|
||||
outerClassName='w-3/6 grow'
|
||||
value={value.address}
|
||||
onChange={handleChange('address')}
|
||||
placeholder={intl.formatMessage(messages.address)}
|
||||
/>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-2/6 flex-grow'
|
||||
outerClassName='w-2/6 grow'
|
||||
value={value.note}
|
||||
onChange={handleChange('note')}
|
||||
placeholder={intl.formatMessage(messages.note)}
|
||||
|
||||
@@ -24,14 +24,14 @@ const PromoPanelInput: StreamfieldComponent<FooterItem> = ({ value, onChange })
|
||||
<HStack space={2} grow>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-full flex-grow'
|
||||
outerClassName='w-full grow'
|
||||
placeholder={intl.formatMessage(messages.label)}
|
||||
value={value.title}
|
||||
onChange={handleChange('title')}
|
||||
/>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-full flex-grow'
|
||||
outerClassName='w-full grow'
|
||||
placeholder={intl.formatMessage(messages.url)}
|
||||
value={value.url}
|
||||
onChange={handleChange('url')}
|
||||
|
||||
@@ -36,14 +36,14 @@ const PromoPanelInput: StreamfieldComponent<PromoPanelItem> = ({ value, onChange
|
||||
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-full flex-grow'
|
||||
outerClassName='w-full grow'
|
||||
placeholder={intl.formatMessage(messages.label)}
|
||||
value={value.text}
|
||||
onChange={handleChange('text')}
|
||||
/>
|
||||
<Input
|
||||
type='text'
|
||||
outerClassName='w-full flex-grow'
|
||||
outerClassName='w-full grow'
|
||||
placeholder={intl.formatMessage(messages.url)}
|
||||
value={value.url}
|
||||
onChange={handleChange('url')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'clsx';
|
||||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
@@ -112,7 +112,7 @@ const Card: React.FC<ICard> = ({
|
||||
|
||||
const interactive = card.type !== 'link';
|
||||
horizontal = typeof horizontal === 'boolean' ? horizontal : interactive || embedded;
|
||||
const className = classnames('status-card', { horizontal, compact, interactive }, `status-card--${card.type}`);
|
||||
const className = clsx('status-card', { horizontal, compact, interactive }, `status-card--${card.type}`);
|
||||
const ratio = getRatio(card);
|
||||
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
|
||||
|
||||
@@ -223,7 +223,7 @@ const Card: React.FC<ICard> = ({
|
||||
);
|
||||
} else if (card.image) {
|
||||
embed = (
|
||||
<div className={classnames(
|
||||
<div className={clsx(
|
||||
'status-card__image',
|
||||
'w-full flex-none rounded-l md:h-auto md:w-auto md:flex-auto',
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
interface IThreadStatus {
|
||||
id: string,
|
||||
contextType?: string,
|
||||
focusedStatusId: string,
|
||||
onMoveUp: (id: string) => void,
|
||||
onMoveDown: (id: string) => void,
|
||||
|
||||
@@ -361,6 +361,7 @@ const Thread: React.FC<IThread> = (props) => {
|
||||
focusedStatusId={status!.id}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
contextType='thread'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -39,4 +39,4 @@ const FloatingActionButton: React.FC<IFloatingActionButton> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default FloatingActionButton;
|
||||
export default FloatingActionButton;
|
||||
|
||||
@@ -162,4 +162,4 @@ class ImageLoader extends React.PureComponent<IImageLoader> {
|
||||
|
||||
}
|
||||
|
||||
export default ImageLoader;
|
||||
export default ImageLoader;
|
||||
|
||||
@@ -4,9 +4,8 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { spring } from 'react-motion';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import StatusContent from 'soapbox/components/status-content';
|
||||
import { HStack, Stack } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import { HStack } from 'soapbox/components/ui';
|
||||
import ReplyIndicator from 'soapbox/features/compose/components/reply-indicator';
|
||||
|
||||
import Motion from '../../util/optional-motion';
|
||||
|
||||
@@ -56,16 +55,7 @@ const ActionsModal: React.FC<IActionsModal> = ({ status, actions, onClick, onClo
|
||||
{({ top }) => (
|
||||
<div className='modal-root__modal actions-modal' style={{ top: `${top}%` }}>
|
||||
{status && (
|
||||
<Stack space={2} className='border-b border-solid border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800'>
|
||||
<AccountContainer
|
||||
key={status.account as string}
|
||||
id={status.account as string}
|
||||
showProfileHoverCard={false}
|
||||
withLinkToProfile={false}
|
||||
timestamp={status.created_at}
|
||||
/>
|
||||
<StatusContent status={status} />
|
||||
</Stack>
|
||||
<ReplyIndicator className='actions-modal__status rounded-b-none' status={status} hideActions />
|
||||
)}
|
||||
|
||||
<ul className={clsx({ 'with-status': !!status })}>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { remoteInteraction } from 'soapbox/actions/interactions';
|
||||
import { Button, Modal, Stack, Text } from 'soapbox/components/ui';
|
||||
import { Button, Form, Input, Modal, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch, useFeatures, useInstance, useRegistrationStatus } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
@@ -104,9 +104,8 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
||||
secondaryText={isOpen ? <FormattedMessage id='account.register' defaultMessage='Sign up' /> : undefined}
|
||||
>
|
||||
<div className='remote-interaction-modal__content'>
|
||||
<form className='simple_form remote-interaction-modal__fields' onSubmit={onSubmit}>
|
||||
<input
|
||||
type='text'
|
||||
<Form className='remote-interaction-modal__fields' onSubmit={onSubmit}>
|
||||
<Input
|
||||
placeholder={intl.formatMessage(messages.accountPlaceholder)}
|
||||
name='remote_follow[acct]'
|
||||
value={account}
|
||||
@@ -116,7 +115,7 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
||||
required
|
||||
/>
|
||||
<Button type='submit' theme='primary'>{button}</Button>
|
||||
</form>
|
||||
</Form>
|
||||
<div className='remote-interaction-modal__divider'>
|
||||
<Text align='center'>
|
||||
<FormattedMessage id='remote_interaction.divider' defaultMessage='or' />
|
||||
|
||||
@@ -56,7 +56,7 @@ const UploadArea: React.FC<IUploadArea> = ({ active, onClose }) => {
|
||||
<Stack space={3} justifyContent='center' alignItems='center'>
|
||||
<Icon
|
||||
src={require('@tabler/icons/cloud-upload.svg')}
|
||||
className='h-12 w-12 text-white text-opacity-90'
|
||||
className='h-12 w-12 text-white/90'
|
||||
/>
|
||||
|
||||
<Text size='xl' theme='white'>
|
||||
|
||||
@@ -72,7 +72,6 @@ import {
|
||||
Lists,
|
||||
Bookmarks,
|
||||
Settings,
|
||||
MediaDisplay,
|
||||
EditProfile,
|
||||
EditEmail,
|
||||
EditPassword,
|
||||
@@ -301,7 +300,6 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||
<WrappedRoute path='/settings/email' page={DefaultPage} component={EditEmail} content={children} />
|
||||
<WrappedRoute path='/settings/password' page={DefaultPage} component={EditPassword} content={children} />
|
||||
<WrappedRoute path='/settings/account' page={DefaultPage} component={DeleteAccount} content={children} />
|
||||
<WrappedRoute path='/settings/media_display' page={DefaultPage} component={MediaDisplay} content={children} />
|
||||
<WrappedRoute path='/settings/mfa' page={DefaultPage} component={MfaForm} exact />
|
||||
<WrappedRoute path='/settings/tokens' page={DefaultPage} component={AuthTokenList} content={children} />
|
||||
<WrappedRoute path='/settings' page={DefaultPage} component={Settings} content={children} />
|
||||
|
||||
@@ -238,10 +238,6 @@ export function Settings() {
|
||||
return import(/* webpackChunkName: "features/settings" */'../../settings');
|
||||
}
|
||||
|
||||
export function MediaDisplay() {
|
||||
return import(/* webpackChunkName: "features/settings" */'../../settings/media-display');
|
||||
}
|
||||
|
||||
export function EditProfile() {
|
||||
return import(/* webpackChunkName: "features/edit_profile" */'../../edit-profile');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user