parent
43acb4f880
commit
3dffc46fc1
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
||||
|
||||
export default @injectIntl
|
||||
class StatusReplyMentions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status } = this.props;
|
||||
|
||||
const to = status.get('mentions', []);
|
||||
|
||||
if (!status.get('in_reply_to_id') || !to || to.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='reply-mentions'>
|
||||
<FormattedMessage
|
||||
id='reply_mentions.reply'
|
||||
defaultMessage='Replying to {accounts}{more}'
|
||||
values={{
|
||||
accounts: to.slice(0, 2).map(account => (<>
|
||||
<HoverRefWrapper accountId={account.get('id')} inline>
|
||||
<Link to={`/@${account.get('acct')}`} className='reply-mentions__account'>@{account.get('acct').split('@')[0]}</Link>
|
||||
</HoverRefWrapper>
|
||||
{' '}
|
||||
</>)),
|
||||
more: to.size > 2 && (
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}/mentions`}>
|
||||
<FormattedMessage id='reply_mentions.more' defaultMessage='and {count} more' values={{ count: to.size - 2 }} />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default @injectIntl
|
||||
class ReplyMentions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onOpenMentionsModal: PropTypes.func.isRequired,
|
||||
explicitAddressing: PropTypes.bool,
|
||||
to: ImmutablePropTypes.orderedSet,
|
||||
isReply: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleClick = e => {
|
||||
e.preventDefault();
|
||||
|
||||
this.props.onOpenMentionsModal();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { explicitAddressing, to, isReply } = this.props;
|
||||
|
||||
if (!explicitAddressing || !isReply || !to || to.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a href='#' className='reply-mentions' onClick={this.handleClick}>
|
||||
<FormattedMessage
|
||||
id='reply_mentions.reply'
|
||||
defaultMessage='Replying to {accounts}{more}'
|
||||
values={{
|
||||
accounts: to.slice(0, 2).map(acct => <><span className='reply-mentions__account'>@{acct.split('@')[0]}</span>{' '}</>),
|
||||
more: to.size > 2 && <FormattedMessage id='reply_mentions.more' defaultMessage='and {count} more' values={{ count: to.size - 2 }} />,
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { makeGetStatus } from 'soapbox/selectors';
|
||||
import { openModal } from 'soapbox/actions/modal';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
import ReplyMentions from '../components/reply_mentions';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
return state => {
|
||||
const instance = state.get('instance');
|
||||
const { explicitAddressing } = getFeatures(instance);
|
||||
|
||||
if (!explicitAddressing) {
|
||||
return {
|
||||
explicitAddressing: false,
|
||||
};
|
||||
}
|
||||
|
||||
const status = getStatus(state, { id: state.getIn(['compose', 'in_reply_to']) });
|
||||
|
||||
if (!status) {
|
||||
return {
|
||||
isReply: false,
|
||||
};
|
||||
}
|
||||
const to = state.getIn(['compose', 'to']);
|
||||
|
||||
return {
|
||||
to,
|
||||
isReply: true,
|
||||
explicitAddressing: true,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onOpenMentionsModal() {
|
||||
dispatch(openModal('REPLY_MENTIONS', {
|
||||
onCancel: () => dispatch(openModal('COMPOSE')),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyMentions);
|
@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import MissingIndicator from '../../components/missing_indicator';
|
||||
import { fetchStatus } from '../../actions/statuses';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import { makeGetStatus } from '../../selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.mentions', defaultMessage: 'Mentions' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const getStatus = makeGetStatus();
|
||||
const status = getStatus(state, {
|
||||
id: props.params.statusId,
|
||||
username: props.params.username,
|
||||
});
|
||||
|
||||
return {
|
||||
status,
|
||||
accountIds: status ? ImmutableOrderedSet(status.get('mentions').map(m => m.get('id'))) : null,
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Mentions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.orderedSet,
|
||||
status: ImmutablePropTypes.map,
|
||||
};
|
||||
|
||||
fetchData = () => {
|
||||
const { dispatch, params } = this.props;
|
||||
const { statusId } = params;
|
||||
|
||||
dispatch(fetchStatus(statusId));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { params } = this.props;
|
||||
|
||||
if (params.statusId !== prevProps.params.statusId) {
|
||||
this.fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, accountIds, status } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column heading={intl.formatMessage(messages.heading)}>
|
||||
<ScrollableList
|
||||
scrollKey='reblogs'
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<AccountContainer key={id} id={id} withNote={false} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Avatar from 'soapbox/components/avatar';
|
||||
import DisplayName from 'soapbox/components/display_name';
|
||||
import IconButton from 'soapbox/components/icon_button';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { addToMentions, removeFromMentions } from 'soapbox/actions/compose';
|
||||
import { fetchAccount } from 'soapbox/actions/accounts';
|
||||
|
||||
const messages = defineMessages({
|
||||
remove: { id: 'reply_mentions.account.remove', defaultMessage: 'Remove from mentions' },
|
||||
add: { id: 'reply_mentions.account.add', defaultMessage: 'Add to mentions' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => {
|
||||
const account = getAccount(state, accountId);
|
||||
|
||||
return {
|
||||
added: !!account && state.getIn(['compose', 'to']).includes(account.get('acct')),
|
||||
account,
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||
onRemove: () => dispatch(removeFromMentions(accountId)),
|
||||
onAdd: () => dispatch(addToMentions(accountId)),
|
||||
fetchAccount: () => dispatch(fetchAccount(accountId)),
|
||||
});
|
||||
|
||||
export default @connect(makeMapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
onAdd: PropTypes.func.isRequired,
|
||||
added: PropTypes.bool,
|
||||
author: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
added: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { account, accountId } = this.props;
|
||||
|
||||
if (accountId && !account) {
|
||||
this.props.fetchAccount(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, intl, onRemove, onAdd, added, author } = this.props;
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
let button;
|
||||
|
||||
if (added) {
|
||||
button = <IconButton src={require('@tabler/icons/icons/x.svg')} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
||||
} else {
|
||||
button = <IconButton src={require('@tabler/icons/icons/plus.svg')} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account'>
|
||||
<div className='account__wrapper'>
|
||||
<div className='account__display-name'>
|
||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||
<DisplayName account={account} />
|
||||
</div>
|
||||
|
||||
<div className='account__relationship'>
|
||||
{!author && button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import IconButton from 'soapbox/components/icon_button';
|
||||
import { statusToMentionsAccountIdsArray } from 'soapbox/reducers/compose';
|
||||
import { makeGetStatus } from 'soapbox/selectors';
|
||||
import Account from '../../reply_mentions/account';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
confirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
return state => {
|
||||
const status = getStatus(state, { id: state.getIn(['compose', 'in_reply_to']) });
|
||||
|
||||
if (!status) {
|
||||
return {
|
||||
isReply: false,
|
||||
};
|
||||
}
|
||||
|
||||
const me = state.get('me');
|
||||
const account = state.getIn(['accounts', me]);
|
||||
|
||||
const mentions = statusToMentionsAccountIdsArray(state, status, account);
|
||||
|
||||
return {
|
||||
mentions,
|
||||
author: status.getIn(['account', 'id']),
|
||||
to: state.getIn(['compose', 'to']),
|
||||
isReply: true,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
class ComposeModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
author: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
inReplyTo: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
onClickClose = () => {
|
||||
const { onClose, onCancel } = this.props;
|
||||
onClose('COMPOSE');
|
||||
if (onCancel) onCancel();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { intl, mentions, author } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal reply-mentions-modal'>
|
||||
<div className='reply-mentions-modal__header'>
|
||||
<IconButton
|
||||
className='reply-mentions-modal__back'
|
||||
src={require('@tabler/icons/icons/arrow-left.svg')}
|
||||
onClick={this.onClickClose}
|
||||
aria-label={intl.formatMessage(messages.close)}
|
||||
title={intl.formatMessage(messages.close)}
|
||||
/>
|
||||
<h3 className='reply-mentions-modal__header__title'>
|
||||
<FormattedMessage id='navigation_bar.in_reply_to' defaultMessage='In reply to' />
|
||||
</h3>
|
||||
</div>
|
||||
<div className='reply-mentions-modal__accounts'>
|
||||
{mentions.map(accountId => <Account key={accountId} accountId={accountId} added author={author === accountId} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps)(ComposeModal));
|
@ -0,0 +1,24 @@
|
||||
.reply-mentions {
|
||||
margin: 0 10px;
|
||||
color: var(--primary-text-color--faint);
|
||||
font-size: 15px;
|
||||
text-decoration: none;
|
||||
|
||||
&__account,
|
||||
a {
|
||||
color: var(--highlight-text-color);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status__wrapper,
|
||||
.detailed-status {
|
||||
.reply-mentions {
|
||||
display: block;
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue