kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
TypeScript, React.FC
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>environments/review-develop-3zknud/deployments/248^2
rodzic
615ff427e4
commit
cdbb88d2e0
|
@ -27,7 +27,7 @@ interface IModal {
|
||||||
/** Callback when the modal is cancelled. */
|
/** Callback when the modal is cancelled. */
|
||||||
cancelAction?: () => void,
|
cancelAction?: () => void,
|
||||||
/** Cancel button text. */
|
/** Cancel button text. */
|
||||||
cancelText?: string,
|
cancelText?: React.ReactNode,
|
||||||
/** URL to an SVG icon for the close button. */
|
/** URL to an SVG icon for the close button. */
|
||||||
closeIcon?: string,
|
closeIcon?: string,
|
||||||
/** Position of the close button. */
|
/** Position of the close button. */
|
||||||
|
|
|
@ -9,13 +9,13 @@ import type { Status } from 'soapbox/types/entities';
|
||||||
|
|
||||||
interface IReplyIndicator {
|
interface IReplyIndicator {
|
||||||
status?: Status,
|
status?: Status,
|
||||||
onCancel: () => void,
|
onCancel?: () => void,
|
||||||
hideActions: boolean,
|
hideActions: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReplyIndicator: React.FC<IReplyIndicator> = ({ status, hideActions, onCancel }) => {
|
const ReplyIndicator: React.FC<IReplyIndicator> = ({ status, hideActions, onCancel }) => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onCancel();
|
onCancel!();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
|
@ -23,7 +23,7 @@ const ReplyIndicator: React.FC<IReplyIndicator> = ({ status, hideActions, onCanc
|
||||||
}
|
}
|
||||||
|
|
||||||
let actions = {};
|
let actions = {};
|
||||||
if (!hideActions) {
|
if (!hideActions && onCancel) {
|
||||||
actions = {
|
actions = {
|
||||||
onActionClick: handleClick,
|
onActionClick: handleClick,
|
||||||
actionIcon: require('@tabler/icons/icons/x.svg'),
|
actionIcon: require('@tabler/icons/icons/x.svg'),
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { getTitle } from '../utils/coin_db';
|
||||||
|
|
||||||
import CryptoIcon from './crypto_icon';
|
import CryptoIcon from './crypto_icon';
|
||||||
|
|
||||||
interface ICryptoAddress {
|
export interface ICryptoAddress {
|
||||||
address: string,
|
address: string,
|
||||||
ticker: string,
|
ticker: string,
|
||||||
note?: string,
|
note?: string,
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { buildStatus } from '../builder';
|
||||||
|
|
||||||
import ScheduledStatusActionBar from './scheduled_status_action_bar';
|
import ScheduledStatusActionBar from './scheduled_status_action_bar';
|
||||||
|
|
||||||
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
|
import type { Account as AccountEntity, Poll as PollEntity, Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
interface IScheduledStatus {
|
interface IScheduledStatus {
|
||||||
statusId: string,
|
statusId: string,
|
||||||
|
@ -55,7 +55,7 @@ const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) =>
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{status.poll && <PollPreview poll={status.poll} />}
|
{status.poll && <PollPreview poll={status.poll as PollEntity} />}
|
||||||
|
|
||||||
<ScheduledStatusActionBar status={status} {...other} />
|
<ScheduledStatusActionBar status={status} {...other} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import Icon from 'soapbox/components/icon';
|
|
||||||
import { Modal, Stack, Text } from 'soapbox/components/ui';
|
|
||||||
import ReplyIndicator from 'soapbox/features/compose/components/reply_indicator';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
|
||||||
reblog: { id: 'status.reblog', defaultMessage: 'Repost' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @injectIntl @withRouter
|
|
||||||
class BoostModal extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
status: ImmutablePropTypes.record.isRequired,
|
|
||||||
onReblog: PropTypes.func.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
history: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleReblog = () => {
|
|
||||||
this.props.onReblog(this.props.status);
|
|
||||||
this.props.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAccountClick = (e) => {
|
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onClose();
|
|
||||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStatusClick = (e) => {
|
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onClose();
|
|
||||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('url')}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { status, intl } = this.props;
|
|
||||||
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title='Repost?'
|
|
||||||
confirmationAction={this.handleReblog}
|
|
||||||
confirmationText={intl.formatMessage(buttonText)}
|
|
||||||
>
|
|
||||||
<Stack space={4}>
|
|
||||||
<ReplyIndicator status={status} hideActions />
|
|
||||||
|
|
||||||
<Text>
|
|
||||||
<FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} />
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import { Modal, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
import ReplyIndicator from 'soapbox/features/compose/components/reply_indicator';
|
||||||
|
|
||||||
|
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
||||||
|
reblog: { id: 'status.reblog', defaultMessage: 'Repost' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IBoostModal {
|
||||||
|
status: StatusEntity,
|
||||||
|
onReblog: (status: StatusEntity) => void,
|
||||||
|
onClose: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoostModal: React.FC<IBoostModal> = ({ status, onReblog, onClose }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const handleReblog = () => {
|
||||||
|
onReblog(status);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonText = status.reblogged ? messages.cancel_reblog : messages.reblog;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title='Repost?'
|
||||||
|
confirmationAction={handleReblog}
|
||||||
|
confirmationText={intl.formatMessage(buttonText)}
|
||||||
|
>
|
||||||
|
<Stack space={4}>
|
||||||
|
<ReplyIndicator status={status} hideActions />
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
<FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} />
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoostModal;
|
|
@ -1,84 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { Modal } from 'soapbox/components/ui';
|
|
||||||
import { SimpleForm, FieldsGroup, Checkbox } from 'soapbox/features/forms';
|
|
||||||
|
|
||||||
export default @injectIntl
|
|
||||||
class ConfirmationModal extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
heading: PropTypes.node,
|
|
||||||
icon: PropTypes.node,
|
|
||||||
message: PropTypes.node.isRequired,
|
|
||||||
confirm: PropTypes.node.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
onConfirm: PropTypes.func.isRequired,
|
|
||||||
secondary: PropTypes.string,
|
|
||||||
onSecondary: PropTypes.func,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onCancel: PropTypes.func,
|
|
||||||
checkbox: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
checked: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.props.onClose('CONFIRM');
|
|
||||||
this.props.onConfirm();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSecondary = () => {
|
|
||||||
this.props.onClose('CONFIRM');
|
|
||||||
this.props.onSecondary();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancel = () => {
|
|
||||||
const { onClose, onCancel } = this.props;
|
|
||||||
onClose('CONFIRM');
|
|
||||||
if (onCancel) onCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCheckboxChange = e => {
|
|
||||||
this.setState({ checked: e.target.checked });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { heading, message, confirm, secondary, checkbox } = this.props;
|
|
||||||
const { checked } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={heading}
|
|
||||||
confirmationAction={this.handleClick}
|
|
||||||
confirmationText={confirm}
|
|
||||||
confirmationDisabled={checkbox && !checked}
|
|
||||||
confirmationTheme='danger'
|
|
||||||
cancelText={<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />}
|
|
||||||
cancelAction={this.handleCancel}
|
|
||||||
secondaryText={secondary}
|
|
||||||
secondaryAction={this.props.onSecondary && this.handleSecondary}
|
|
||||||
>
|
|
||||||
<p className='text-gray-600 dark:text-gray-300'>{message}</p>
|
|
||||||
|
|
||||||
<div className='mt-2'>
|
|
||||||
{checkbox && <div className='confirmation-modal__checkbox'>
|
|
||||||
<SimpleForm>
|
|
||||||
<FieldsGroup>
|
|
||||||
<Checkbox
|
|
||||||
onChange={this.handleCheckboxChange}
|
|
||||||
label={checkbox}
|
|
||||||
checked={checked}
|
|
||||||
/>
|
|
||||||
</FieldsGroup>
|
|
||||||
</SimpleForm>
|
|
||||||
</div>}
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Modal } from 'soapbox/components/ui';
|
||||||
|
import { SimpleForm, FieldsGroup, Checkbox } from 'soapbox/features/forms';
|
||||||
|
|
||||||
|
interface IConfirmationModal {
|
||||||
|
heading: React.ReactNode,
|
||||||
|
message: React.ReactNode,
|
||||||
|
confirm: React.ReactNode,
|
||||||
|
onClose: (type: string) => void,
|
||||||
|
onConfirm: () => void,
|
||||||
|
secondary: React.ReactNode,
|
||||||
|
onSecondary?: () => void,
|
||||||
|
onCancel: () => void,
|
||||||
|
checkbox?: JSX.Element,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmationModal: React.FC<IConfirmationModal> = ({
|
||||||
|
heading,
|
||||||
|
message,
|
||||||
|
confirm,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
secondary,
|
||||||
|
onSecondary,
|
||||||
|
onCancel,
|
||||||
|
checkbox,
|
||||||
|
}) => {
|
||||||
|
const [checked, setChecked] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
onClose('CONFIRM');
|
||||||
|
onConfirm();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSecondary = () => {
|
||||||
|
onClose('CONFIRM');
|
||||||
|
onSecondary!();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
onClose('CONFIRM');
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckboxChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||||
|
setChecked(e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={heading}
|
||||||
|
confirmationAction={handleClick}
|
||||||
|
confirmationText={confirm}
|
||||||
|
confirmationDisabled={checkbox && !checked}
|
||||||
|
confirmationTheme='danger'
|
||||||
|
cancelText={<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />}
|
||||||
|
cancelAction={handleCancel}
|
||||||
|
secondaryText={secondary}
|
||||||
|
secondaryAction={onSecondary && handleSecondary}
|
||||||
|
>
|
||||||
|
<p className='text-gray-600 dark:text-gray-300'>{message}</p>
|
||||||
|
|
||||||
|
<div className='mt-2'>
|
||||||
|
{checkbox && <div className='confirmation-modal__checkbox'>
|
||||||
|
<SimpleForm>
|
||||||
|
<FieldsGroup>
|
||||||
|
<Checkbox
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
label={checkbox}
|
||||||
|
checked={checked}
|
||||||
|
/>
|
||||||
|
</FieldsGroup>
|
||||||
|
</SimpleForm>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmationModal;
|
|
@ -1,22 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import DetailedCryptoAddress from 'soapbox/features/crypto_donate/components/detailed_crypto_address';
|
|
||||||
|
|
||||||
export default class CryptoDonateModal extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
address: PropTypes.string.isRequired,
|
|
||||||
ticker: PropTypes.string.isRequired,
|
|
||||||
note: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal crypto-donate-modal'>
|
|
||||||
<DetailedCryptoAddress {...this.props} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import DetailedCryptoAddress from 'soapbox/features/crypto_donate/components/detailed_crypto_address';
|
||||||
|
|
||||||
|
import type { ICryptoAddress } from '../../crypto_donate/components/crypto_address';
|
||||||
|
|
||||||
|
const CryptoDonateModal: React.FC<ICryptoAddress> = (props) => {
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal crypto-donate-modal'>
|
||||||
|
<DetailedCryptoAddress {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CryptoDonateModal;
|
|
@ -1,56 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { NavLink, withRouter } from 'react-router-dom';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
import { fetchLists } from 'soapbox/actions/lists';
|
|
||||||
import Icon from 'soapbox/components/icon';
|
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
|
||||||
if (!lists) {
|
|
||||||
return lists;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
lists: getOrderedLists(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @withRouter
|
|
||||||
@connect(mapStateToProps)
|
|
||||||
class ListPanel extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
lists: ImmutablePropTypes.list,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
dispatch(fetchLists());
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { lists } = this.props;
|
|
||||||
|
|
||||||
if (!lists || lists.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
{lists.map(list => (
|
|
||||||
<NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/list/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
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;
|
|
@ -1,108 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
|
|
||||||
import { muteAccount } from 'soapbox/actions/accounts';
|
|
||||||
import { closeModal } from 'soapbox/actions/modals';
|
|
||||||
import { toggleHideNotifications } from 'soapbox/actions/mutes';
|
|
||||||
import { Modal, HStack, Stack, Text } from 'soapbox/components/ui';
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
return {
|
|
||||||
isSubmitting: state.reports.new.isSubmitting,
|
|
||||||
account: state.getIn(['mutes', 'new', 'account']),
|
|
||||||
notifications: state.getIn(['mutes', 'new', 'notifications']),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
|
||||||
return {
|
|
||||||
onConfirm(account, notifications) {
|
|
||||||
dispatch(muteAccount(account.get('id'), notifications));
|
|
||||||
},
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
dispatch(closeModal());
|
|
||||||
},
|
|
||||||
|
|
||||||
onToggleNotifications() {
|
|
||||||
dispatch(toggleHideNotifications());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
@injectIntl
|
|
||||||
class MuteModal extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
isSubmitting: PropTypes.bool.isRequired,
|
|
||||||
account: PropTypes.object.isRequired,
|
|
||||||
notifications: PropTypes.bool.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
onConfirm: PropTypes.func.isRequired,
|
|
||||||
onToggleNotifications: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.props.onClose();
|
|
||||||
this.props.onConfirm(this.props.account, this.props.notifications);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancel = () => {
|
|
||||||
this.props.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleNotifications = () => {
|
|
||||||
this.props.onToggleNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { account, notifications } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={
|
|
||||||
<FormattedMessage
|
|
||||||
id='confirmations.mute.heading'
|
|
||||||
defaultMessage='Mute @{name}'
|
|
||||||
values={{ name: account.get('acct') }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onClose={this.handleCancel}
|
|
||||||
confirmationAction={this.handleClick}
|
|
||||||
confirmationText={<FormattedMessage id='confirmations.mute.confirm' defaultMessage='Mute' />}
|
|
||||||
cancelText={<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />}
|
|
||||||
cancelAction={this.handleCancel}
|
|
||||||
>
|
|
||||||
<Stack space={4}>
|
|
||||||
<Text>
|
|
||||||
<FormattedMessage
|
|
||||||
id='confirmations.mute.message'
|
|
||||||
defaultMessage='Are you sure you want to mute {name}?'
|
|
||||||
values={{ name: <strong>@{account.get('acct')}</strong> }}
|
|
||||||
/>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<HStack alignItems='center' space={2}>
|
|
||||||
<Text tag='span'>
|
|
||||||
<FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' />
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Toggle
|
|
||||||
checked={notifications}
|
|
||||||
onChange={this.toggleNotifications}
|
|
||||||
icons={false}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</label>
|
|
||||||
</Stack>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
|
||||||
|
import { muteAccount } from 'soapbox/actions/accounts';
|
||||||
|
import { closeModal } from 'soapbox/actions/modals';
|
||||||
|
import { toggleHideNotifications } from 'soapbox/actions/mutes';
|
||||||
|
import { Modal, HStack, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
import { makeGetAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
const MuteModal = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const account = useAppSelector((state) => getAccount(state, state.mutes.new.accountId!));
|
||||||
|
const notifications = useAppSelector((state) => state.mutes.new.notifications);
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
dispatch(closeModal());
|
||||||
|
dispatch(muteAccount(account.id, notifications));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
dispatch(closeModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleNotifications = () => {
|
||||||
|
dispatch(toggleHideNotifications());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='confirmations.mute.heading'
|
||||||
|
defaultMessage='Mute @{name}'
|
||||||
|
values={{ name: account.acct }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClose={handleCancel}
|
||||||
|
confirmationAction={handleClick}
|
||||||
|
confirmationText={<FormattedMessage id='confirmations.mute.confirm' defaultMessage='Mute' />}
|
||||||
|
cancelText={<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />}
|
||||||
|
cancelAction={handleCancel}
|
||||||
|
>
|
||||||
|
<Stack space={4}>
|
||||||
|
<Text>
|
||||||
|
<FormattedMessage
|
||||||
|
id='confirmations.mute.message'
|
||||||
|
defaultMessage='Are you sure you want to mute {name}?'
|
||||||
|
values={{ name: <strong>@{account.acct}</strong> }}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<HStack alignItems='center' space={2}>
|
||||||
|
<Text tag='span'>
|
||||||
|
<FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' />
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Toggle
|
||||||
|
checked={notifications}
|
||||||
|
onChange={toggleNotifications}
|
||||||
|
icons={false}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</label>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MuteModal;
|
|
@ -14,7 +14,7 @@ import { buildStatus } from '../util/pending_status_builder';
|
||||||
|
|
||||||
import PollPreview from './poll_preview';
|
import PollPreview from './poll_preview';
|
||||||
|
|
||||||
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
|
import type { Account as AccountEntity, Poll as PollEntity, Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const shouldHaveCard = (pendingStatus: StatusEntity) => {
|
const shouldHaveCard = (pendingStatus: StatusEntity) => {
|
||||||
return Boolean(pendingStatus.content.match(/https?:\/\/\S*/));
|
return Boolean(pendingStatus.content.match(/https?:\/\/\S*/));
|
||||||
|
@ -81,7 +81,7 @@ const PendingStatus: React.FC<IPendingStatus> = ({ idempotencyKey, className, mu
|
||||||
|
|
||||||
<PendingStatusMedia status={status} />
|
<PendingStatusMedia status={status} />
|
||||||
|
|
||||||
{status.poll && <PollPreview poll={status.poll} />}
|
{status.poll && <PollPreview poll={status.poll as PollEntity} />}
|
||||||
|
|
||||||
{status.quote && <QuotedStatus statusId={status.quote as string} />}
|
{status.quote && <QuotedStatus statusId={status.quote as string} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
export default class PollPreview extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
poll: ImmutablePropTypes.map,
|
|
||||||
};
|
|
||||||
|
|
||||||
renderOption(option) {
|
|
||||||
const { poll } = this.props;
|
|
||||||
const showResults = poll.get('voted') || poll.get('expired');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={option}>
|
|
||||||
<label className={classNames('poll__text', { selectable: !showResults })}>
|
|
||||||
<input
|
|
||||||
name='vote-options'
|
|
||||||
type={poll.get('multiple') ? 'checkbox' : 'radio'}
|
|
||||||
onChange={this.handleOptionChange}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className={classNames('poll__input', { checkbox: poll.get('multiple') })} />
|
|
||||||
|
|
||||||
<span dangerouslySetInnerHTML={{ __html: option }} />
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { poll } = this.props;
|
|
||||||
|
|
||||||
if (!poll) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='poll'>
|
|
||||||
<ul>
|
|
||||||
{poll.get('options').map((option, i) => this.renderOption(option, i))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Poll as PollEntity, PollOption as PollOptionEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
interface IPollPreview {
|
||||||
|
poll: PollEntity,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PollPreview: React.FC<IPollPreview> = ({ poll }) => {
|
||||||
|
const renderOption = (option: PollOptionEntity, index: number) => {
|
||||||
|
const showResults = poll.voted || poll.expired;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={index}>
|
||||||
|
<label className={classNames('poll__text', { selectable: !showResults })}>
|
||||||
|
<input
|
||||||
|
name='vote-options'
|
||||||
|
type={poll.multiple ? 'checkbox' : 'radio'}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className={classNames('poll__input', { checkbox: poll.multiple })} />
|
||||||
|
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: option.title_emojified }} />
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!poll) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='poll'>
|
||||||
|
<ul>
|
||||||
|
{poll.options.map((option, i) => renderOption(option, i))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PollPreview;
|
|
@ -1,24 +1,30 @@
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MUTES_INIT_MODAL,
|
MUTES_INIT_MODAL,
|
||||||
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
|
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
|
||||||
} from '../actions/mutes';
|
} from '../actions/mutes';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
import type { AnyAction } from 'redux';
|
||||||
new: ImmutableMap({
|
|
||||||
isSubmitting: false,
|
const NewMuteRecord = ImmutableRecord({
|
||||||
account: null,
|
isSubmitting: false,
|
||||||
notifications: true,
|
accountId: null,
|
||||||
}),
|
notifications: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function mutes(state = initialState, action) {
|
const ReducerRecord = ImmutableRecord({
|
||||||
|
new: NewMuteRecord(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type State = ReturnType<typeof ReducerRecord>;
|
||||||
|
|
||||||
|
export default function mutes(state: State = ReducerRecord(), action: AnyAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case MUTES_INIT_MODAL:
|
case MUTES_INIT_MODAL:
|
||||||
return state.withMutations((state) => {
|
return state.withMutations((state) => {
|
||||||
state.setIn(['new', 'isSubmitting'], false);
|
state.setIn(['new', 'isSubmitting'], false);
|
||||||
state.setIn(['new', 'account'], action.account);
|
state.setIn(['new', 'accountId'], action.account.id);
|
||||||
state.setIn(['new', 'notifications'], true);
|
state.setIn(['new', 'notifications'], true);
|
||||||
});
|
});
|
||||||
case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
|
case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
|
Ładowanie…
Reference in New Issue