Support explicit addressing
This commit is contained in:
parent
43acb4f880
commit
3dffc46fc1
|
@ -68,6 +68,9 @@ export const COMPOSE_SCHEDULE_ADD = 'COMPOSE_SCHEDULE_ADD';
|
||||||
export const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET';
|
export const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET';
|
||||||
export const COMPOSE_SCHEDULE_REMOVE = 'COMPOSE_SCHEDULE_REMOVE';
|
export const COMPOSE_SCHEDULE_REMOVE = 'COMPOSE_SCHEDULE_REMOVE';
|
||||||
|
|
||||||
|
export const COMPOSE_ADD_TO_MENTIONS = 'COMPOSE_ADD_TO_MENTIONS';
|
||||||
|
export const COMPOSE_REMOVE_FROM_MENTIONS = 'COMPOSE_REMOVE_FROM_MENTIONS';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||||
|
@ -93,10 +96,14 @@ export function changeCompose(text) {
|
||||||
export function replyCompose(status, routerHistory) {
|
export function replyCompose(status, routerHistory) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
const instance = state.get('instance');
|
||||||
|
const { explicitAddressing } = getFeatures(instance);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: COMPOSE_REPLY,
|
type: COMPOSE_REPLY,
|
||||||
status: status,
|
status: status,
|
||||||
account: state.getIn(['accounts', state.get('me')]),
|
account: state.getIn(['accounts', state.get('me')]),
|
||||||
|
explicitAddressing,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(openModal('COMPOSE'));
|
dispatch(openModal('COMPOSE'));
|
||||||
|
@ -183,6 +190,7 @@ export function submitCompose(routerHistory, force = false) {
|
||||||
|
|
||||||
const status = state.getIn(['compose', 'text'], '');
|
const status = state.getIn(['compose', 'text'], '');
|
||||||
const media = state.getIn(['compose', 'media_attachments']);
|
const media = state.getIn(['compose', 'media_attachments']);
|
||||||
|
let to = state.getIn(['compose', 'to'], null);
|
||||||
|
|
||||||
if (!validateSchedule(state)) {
|
if (!validateSchedule(state)) {
|
||||||
dispatch(snackbar.error(messages.scheduleError));
|
dispatch(snackbar.error(messages.scheduleError));
|
||||||
|
@ -200,6 +208,13 @@ export function submitCompose(routerHistory, force = false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (to && status) {
|
||||||
|
const mentions = status.match(/(?:^|\s|\.)@([a-z0-9_]+(?:@[a-z0-9\.\-]+)?)/g); // not a perfect regex
|
||||||
|
|
||||||
|
if (mentions)
|
||||||
|
to = to.union(mentions.map(mention => mention.trim().slice(1)));
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(submitComposeRequest());
|
dispatch(submitComposeRequest());
|
||||||
dispatch(closeModal());
|
dispatch(closeModal());
|
||||||
|
|
||||||
|
@ -215,6 +230,7 @@ export function submitCompose(routerHistory, force = false) {
|
||||||
content_type: state.getIn(['compose', 'content_type']),
|
content_type: state.getIn(['compose', 'content_type']),
|
||||||
poll: state.getIn(['compose', 'poll'], null),
|
poll: state.getIn(['compose', 'poll'], null),
|
||||||
scheduled_at: state.getIn(['compose', 'schedule'], null),
|
scheduled_at: state.getIn(['compose', 'schedule'], null),
|
||||||
|
to,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(createStatus(params, idempotencyKey)).then(function(data) {
|
dispatch(createStatus(params, idempotencyKey)).then(function(data) {
|
||||||
|
@ -643,3 +659,27 @@ export function openComposeWithText(text = '') {
|
||||||
dispatch(changeCompose(text));
|
dispatch(changeCompose(text));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addToMentions(accountId) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const acct = state.getIn(['accounts', accountId, 'acct']);
|
||||||
|
|
||||||
|
return dispatch({
|
||||||
|
type: COMPOSE_ADD_TO_MENTIONS,
|
||||||
|
account: acct,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFromMentions(accountId) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const acct = state.getIn(['accounts', accountId, 'acct']);
|
||||||
|
|
||||||
|
return dispatch({
|
||||||
|
type: COMPOSE_REMOVE_FROM_MENTIONS,
|
||||||
|
account: acct,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { deleteFromTimelines } from './timelines';
|
||||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||||
import { openModal } from './modal';
|
import { openModal } from './modal';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
import { shouldHaveCard } from 'soapbox/utils/status';
|
import { shouldHaveCard } from 'soapbox/utils/status';
|
||||||
|
|
||||||
export const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST';
|
export const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST';
|
||||||
|
@ -95,10 +96,17 @@ export function fetchStatus(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function redraft(status, raw_text) {
|
export function redraft(status, raw_text) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: REDRAFT,
|
const state = getState();
|
||||||
status,
|
const instance = state.get('instance');
|
||||||
raw_text,
|
const { explicitAddressing } = getFeatures(instance);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: REDRAFT,
|
||||||
|
status,
|
||||||
|
raw_text,
|
||||||
|
explicitAddressing,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import RelativeTimestamp from './relative_timestamp';
|
||||||
import DisplayName from './display_name';
|
import DisplayName from './display_name';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
|
import StatusReplyMentions from './status_reply_mentions';
|
||||||
import AttachmentThumbs from './attachment_thumbs';
|
import AttachmentThumbs from './attachment_thumbs';
|
||||||
import Card from '../features/status/components/card';
|
import Card from '../features/status/components/card';
|
||||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
@ -538,6 +539,8 @@ class Status extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<StatusReplyMentions status={this._properStatus()} />
|
||||||
|
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={status}
|
status={status}
|
||||||
reblogContent={reblogContent}
|
reblogContent={reblogContent}
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
||||||
|
import ReplyMentions from '../containers/reply_mentions_container';
|
||||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
import AutosuggestInput from '../../../components/autosuggest_input';
|
||||||
import PollButtonContainer from '../containers/poll_button_container';
|
import PollButtonContainer from '../containers/poll_button_container';
|
||||||
|
@ -308,7 +309,9 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
<WarningContainer />
|
<WarningContainer />
|
||||||
|
|
||||||
{ !shouldCondense && <ReplyIndicatorContainer /> }
|
{!shouldCondense && <ReplyIndicatorContainer />}
|
||||||
|
|
||||||
|
{!shouldCondense && <ReplyMentions />}
|
||||||
|
|
||||||
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
|
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
|
||||||
<AutosuggestInput
|
<AutosuggestInput
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
import Avatar from '../../../components/avatar';
|
import Avatar from '../../../components/avatar';
|
||||||
import DisplayName from '../../../components/display_name';
|
import DisplayName from '../../../components/display_name';
|
||||||
import StatusContent from '../../../components/status_content';
|
import StatusContent from '../../../components/status_content';
|
||||||
|
import StatusReplyMentions from '../../../components/status_reply_mentions';
|
||||||
import MediaGallery from '../../../components/media_gallery';
|
import MediaGallery from '../../../components/media_gallery';
|
||||||
import { Link, NavLink } from 'react-router-dom';
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
import { FormattedDate } from 'react-intl';
|
import { FormattedDate } from 'react-intl';
|
||||||
|
@ -18,7 +20,8 @@ import StatusInteractionBar from './status_interaction_bar';
|
||||||
import { getDomain } from 'soapbox/utils/accounts';
|
import { getDomain } from 'soapbox/utils/accounts';
|
||||||
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
||||||
|
|
||||||
export default class DetailedStatus extends ImmutablePureComponent {
|
export default @injectIntl
|
||||||
|
class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
|
@ -82,6 +85,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
window.open(href, 'soapbox-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
window.open(href, 'soapbox-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
|
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
|
||||||
const outerStyle = { boxSizing: 'border-box' };
|
const outerStyle = { boxSizing: 'border-box' };
|
||||||
|
@ -185,6 +189,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<StatusReplyMentions status={status} />
|
||||||
|
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={status}
|
status={status}
|
||||||
expanded={!status.get('hidden')}
|
expanded={!status.get('hidden')}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
FocalPointModal,
|
FocalPointModal,
|
||||||
HotkeysModal,
|
HotkeysModal,
|
||||||
ComposeModal,
|
ComposeModal,
|
||||||
|
ReplyMentionsModal,
|
||||||
UnauthorizedModal,
|
UnauthorizedModal,
|
||||||
EditFederationModal,
|
EditFederationModal,
|
||||||
ComponentModal,
|
ComponentModal,
|
||||||
|
@ -41,6 +42,7 @@ const MODAL_COMPONENTS = {
|
||||||
'LIST_ADDER': ListAdder,
|
'LIST_ADDER': ListAdder,
|
||||||
'HOTKEYS': HotkeysModal,
|
'HOTKEYS': HotkeysModal,
|
||||||
'COMPOSE': ComposeModal,
|
'COMPOSE': ComposeModal,
|
||||||
|
'REPLY_MENTIONS': ReplyMentionsModal,
|
||||||
'UNAUTHORIZED': UnauthorizedModal,
|
'UNAUTHORIZED': UnauthorizedModal,
|
||||||
'CRYPTO_DONATE': CryptoDonateModal,
|
'CRYPTO_DONATE': CryptoDonateModal,
|
||||||
'EDIT_FEDERATION': EditFederationModal,
|
'EDIT_FEDERATION': EditFederationModal,
|
||||||
|
|
|
@ -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));
|
|
@ -58,6 +58,7 @@ import {
|
||||||
Following,
|
Following,
|
||||||
Reblogs,
|
Reblogs,
|
||||||
Reactions,
|
Reactions,
|
||||||
|
Mentions,
|
||||||
Favourites,
|
Favourites,
|
||||||
DirectTimeline,
|
DirectTimeline,
|
||||||
Conversations,
|
Conversations,
|
||||||
|
@ -300,6 +301,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
<WrappedRoute path='/@:username/posts/:statusId/reblogs' page={DefaultPage} component={Reblogs} content={children} />
|
<WrappedRoute path='/@:username/posts/:statusId/reblogs' page={DefaultPage} component={Reblogs} content={children} />
|
||||||
<WrappedRoute path='/@:username/posts/:statusId/likes' page={DefaultPage} component={Favourites} content={children} />
|
<WrappedRoute path='/@:username/posts/:statusId/likes' page={DefaultPage} component={Favourites} content={children} />
|
||||||
<WrappedRoute path='/@:username/posts/:statusId/reactions/:reaction?' page={DefaultPage} component={Reactions} content={children} />
|
<WrappedRoute path='/@:username/posts/:statusId/reactions/:reaction?' page={DefaultPage} component={Reactions} content={children} />
|
||||||
|
<WrappedRoute path='/@:username/posts/:statusId/mentions' page={DefaultPage} component={Mentions} content={children} />
|
||||||
<Redirect from='/@:username/:statusId' to='/@:username/posts/:statusId' />
|
<Redirect from='/@:username/:statusId' to='/@:username/posts/:statusId' />
|
||||||
|
|
||||||
<WrappedRoute path='/statuses/new' page={DefaultPage} component={NewStatus} content={children} exact />
|
<WrappedRoute path='/statuses/new' page={DefaultPage} component={NewStatus} content={children} exact />
|
||||||
|
|
|
@ -102,6 +102,10 @@ export function Reactions() {
|
||||||
return import(/* webpackChunkName: "features/reactions" */'../../reactions');
|
return import(/* webpackChunkName: "features/reactions" */'../../reactions');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Mentions() {
|
||||||
|
return import(/* webpackChunkName: "features/mentions" */'../../mentions');
|
||||||
|
}
|
||||||
|
|
||||||
export function Favourites() {
|
export function Favourites() {
|
||||||
return import(/* webpackChunkName: "features/favourites" */'../../favourites');
|
return import(/* webpackChunkName: "features/favourites" */'../../favourites');
|
||||||
}
|
}
|
||||||
|
@ -190,6 +194,10 @@ export function ComposeModal() {
|
||||||
return import(/* webpackChunkName: "features/ui" */'../components/compose_modal');
|
return import(/* webpackChunkName: "features/ui" */'../components/compose_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ReplyMentionsModal() {
|
||||||
|
return import(/* webpackChunkName: "features/ui" */'../components/reply_mentions_modal');
|
||||||
|
}
|
||||||
|
|
||||||
export function UnauthorizedModal() {
|
export function UnauthorizedModal() {
|
||||||
return import(/* webpackChunkName: "features/ui" */'../components/unauthorized_modal');
|
return import(/* webpackChunkName: "features/ui" */'../components/unauthorized_modal');
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,6 +209,7 @@
|
||||||
"column.import_data": "Importuj dane",
|
"column.import_data": "Importuj dane",
|
||||||
"column.info": "Informacje o serwerze",
|
"column.info": "Informacje o serwerze",
|
||||||
"column.lists": "Listy",
|
"column.lists": "Listy",
|
||||||
|
"column.mentions": "W odpowiedzi do",
|
||||||
"column.mfa": "Uwierzytelnianie wieloetapowe",
|
"column.mfa": "Uwierzytelnianie wieloetapowe",
|
||||||
"column.mfa_cancel": "Anuluj",
|
"column.mfa_cancel": "Anuluj",
|
||||||
"column.mfa_confirm_button": "Potwierdź",
|
"column.mfa_confirm_button": "Potwierdź",
|
||||||
|
@ -610,6 +611,7 @@
|
||||||
"navigation_bar.mutes": "Wyciszeni użytkownicy",
|
"navigation_bar.mutes": "Wyciszeni użytkownicy",
|
||||||
"navigation_bar.pins": "Przypięte wpisy",
|
"navigation_bar.pins": "Przypięte wpisy",
|
||||||
"navigation_bar.preferences": "Preferencje",
|
"navigation_bar.preferences": "Preferencje",
|
||||||
|
"navigation_bar.in_reply_to": "W odpowiedzi do",
|
||||||
"navigation_bar.security": "Bezpieczeństwo",
|
"navigation_bar.security": "Bezpieczeństwo",
|
||||||
"navigation_bar.soapbox_config": "Konfiguracja Soapbox",
|
"navigation_bar.soapbox_config": "Konfiguracja Soapbox",
|
||||||
"notification.chat_mention": "{name} wysłał(a) Ci wiadomośść",
|
"notification.chat_mention": "{name} wysłał(a) Ci wiadomośść",
|
||||||
|
@ -754,6 +756,10 @@
|
||||||
"remote_interaction.user_not_found_error": "Nie można odnaleźć podanego użytkownika",
|
"remote_interaction.user_not_found_error": "Nie można odnaleźć podanego użytkownika",
|
||||||
"remote_timeline.filter_message": "Przeglądasz oś czasu {instance}",
|
"remote_timeline.filter_message": "Przeglądasz oś czasu {instance}",
|
||||||
"reply_indicator.cancel": "Anuluj",
|
"reply_indicator.cancel": "Anuluj",
|
||||||
|
"reply_mentions.account.add": "Dodaj do wspomnianych",
|
||||||
|
"reply_mentions.account.remove": "Usuń z wspomnianych",
|
||||||
|
"reply_mentions.more": "i {count} więcej",
|
||||||
|
"reply_mentions.reply": "W odpowiedzi do {accounts}{more}",
|
||||||
"report.block": "Zablokuj {target}",
|
"report.block": "Zablokuj {target}",
|
||||||
"report.block_hint": "Czy chcesz też zablokować to konto?",
|
"report.block_hint": "Czy chcesz też zablokować to konto?",
|
||||||
"report.forward": "Przekaż na {target}",
|
"report.forward": "Przekaż na {target}",
|
||||||
|
|
|
@ -39,6 +39,8 @@ import {
|
||||||
COMPOSE_POLL_OPTION_CHANGE,
|
COMPOSE_POLL_OPTION_CHANGE,
|
||||||
COMPOSE_POLL_OPTION_REMOVE,
|
COMPOSE_POLL_OPTION_REMOVE,
|
||||||
COMPOSE_POLL_SETTINGS_CHANGE,
|
COMPOSE_POLL_SETTINGS_CHANGE,
|
||||||
|
COMPOSE_ADD_TO_MENTIONS,
|
||||||
|
COMPOSE_REMOVE_FROM_MENTIONS,
|
||||||
} from '../actions/compose';
|
} from '../actions/compose';
|
||||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||||
import { REDRAFT } from '../actions/statuses';
|
import { REDRAFT } from '../actions/statuses';
|
||||||
|
@ -84,7 +86,7 @@ const initialPoll = ImmutableMap({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
function statusToTextMentions(state, status, account) {
|
const statusToTextMentions = (state, status, account) => {
|
||||||
const author = status.getIn(['account', 'acct']);
|
const author = status.getIn(['account', 'acct']);
|
||||||
const mentions = status.get('mentions', []).map(m => m.get('acct'));
|
const mentions = status.get('mentions', []).map(m => m.get('acct'));
|
||||||
|
|
||||||
|
@ -93,12 +95,31 @@ function statusToTextMentions(state, status, account) {
|
||||||
.delete(account.get('acct'))
|
.delete(account.get('acct'))
|
||||||
.map(m => `@${m} `)
|
.map(m => `@${m} `)
|
||||||
.join('');
|
.join('');
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const statusToMentionsArray = (state, status, account) => {
|
||||||
|
const author = status.getIn(['account', 'acct']);
|
||||||
|
const mentions = status.get('mentions', []).map(m => m.get('acct'));
|
||||||
|
|
||||||
|
return ImmutableOrderedSet([author])
|
||||||
|
.concat(mentions)
|
||||||
|
.delete(account.get('acct'));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const statusToMentionsAccountIdsArray = (state, status, account) => {
|
||||||
|
const author = status.getIn(['account', 'id']);
|
||||||
|
const mentions = status.get('mentions', []).map(m => m.get('id'));
|
||||||
|
|
||||||
|
return ImmutableOrderedSet([author])
|
||||||
|
.concat(mentions)
|
||||||
|
.delete(account.get('id'));
|
||||||
|
};
|
||||||
|
|
||||||
function clearAll(state) {
|
function clearAll(state) {
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.set('id', null);
|
map.set('id', null);
|
||||||
map.set('text', '');
|
map.set('text', '');
|
||||||
|
map.set('to', ImmutableOrderedSet());
|
||||||
map.set('spoiler', false);
|
map.set('spoiler', false);
|
||||||
map.set('spoiler_text', '');
|
map.set('spoiler_text', '');
|
||||||
map.set('content_type', state.get('default_content_type'));
|
map.set('content_type', state.get('default_content_type'));
|
||||||
|
@ -197,6 +218,17 @@ const expandMentions = status => {
|
||||||
return fragment.innerHTML;
|
return fragment.innerHTML;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getExplicitMentions = (me, status) => {
|
||||||
|
const fragment = domParser.parseFromString(status.get('content'), 'text/html').documentElement;
|
||||||
|
|
||||||
|
const mentions = status
|
||||||
|
.get('mentions')
|
||||||
|
.filter(mention => !(fragment.querySelector(`a[href="${mention.get('url')}"]`) || mention.get('id') === me))
|
||||||
|
.map(m => m.get('acct'));
|
||||||
|
|
||||||
|
return ImmutableOrderedSet(mentions);
|
||||||
|
};
|
||||||
|
|
||||||
const getAccountSettings = account => {
|
const getAccountSettings = account => {
|
||||||
return account.getIn(['pleroma', 'settings_store', FE_NAME], ImmutableMap());
|
return account.getIn(['pleroma', 'settings_store', FE_NAME], ImmutableMap());
|
||||||
};
|
};
|
||||||
|
@ -290,7 +322,8 @@ export default function compose(state = initialState, action) {
|
||||||
case COMPOSE_REPLY:
|
case COMPOSE_REPLY:
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.set('in_reply_to', action.status.get('id'));
|
map.set('in_reply_to', action.status.get('id'));
|
||||||
map.set('text', statusToTextMentions(state, action.status, action.account));
|
map.set('to', action.explicitAddressing ? statusToMentionsArray(state, action.status, action.account) : undefined);
|
||||||
|
map.set('text', !action.explicitAddressing ? statusToTextMentions(state, action.status, action.account) : '');
|
||||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||||
map.set('focusDate', new Date());
|
map.set('focusDate', new Date());
|
||||||
map.set('caretPosition', null);
|
map.set('caretPosition', null);
|
||||||
|
@ -373,6 +406,7 @@ export default function compose(state = initialState, action) {
|
||||||
case REDRAFT:
|
case REDRAFT:
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status)));
|
map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status)));
|
||||||
|
map.set('to', action.explicitAddressing ? getExplicitMentions(action.status.get('account', 'id'), action.status) : null);
|
||||||
map.set('in_reply_to', action.status.get('in_reply_to_id'));
|
map.set('in_reply_to', action.status.get('in_reply_to_id'));
|
||||||
map.set('privacy', action.status.get('visibility'));
|
map.set('privacy', action.status.get('visibility'));
|
||||||
// TODO: Actually fix this rather than just removing it
|
// TODO: Actually fix this rather than just removing it
|
||||||
|
@ -416,6 +450,10 @@ export default function compose(state = initialState, action) {
|
||||||
return state.updateIn(['poll', 'options'], options => options.delete(action.index));
|
return state.updateIn(['poll', 'options'], options => options.delete(action.index));
|
||||||
case COMPOSE_POLL_SETTINGS_CHANGE:
|
case COMPOSE_POLL_SETTINGS_CHANGE:
|
||||||
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
||||||
|
case COMPOSE_ADD_TO_MENTIONS:
|
||||||
|
return state.update('to', mentions => mentions.add(action.account));
|
||||||
|
case COMPOSE_REMOVE_FROM_MENTIONS:
|
||||||
|
return state.update('to', mentions => mentions.delete(action.account));
|
||||||
case ME_FETCH_SUCCESS:
|
case ME_FETCH_SUCCESS:
|
||||||
return importAccount(state, action.me);
|
return importAccount(state, action.me);
|
||||||
case ME_PATCH_SUCCESS:
|
case ME_PATCH_SUCCESS:
|
||||||
|
|
|
@ -75,6 +75,7 @@ export const getFeatures = createSelector([
|
||||||
v.software === PLEROMA && gte(v.version, '2.4.50'),
|
v.software === PLEROMA && gte(v.version, '2.4.50'),
|
||||||
]),
|
]),
|
||||||
remoteInteractionsAPI: v.software === PLEROMA && gte(v.version, '2.4.50'),
|
remoteInteractionsAPI: v.software === PLEROMA && gte(v.version, '2.4.50'),
|
||||||
|
explicitAddressing: v.software === PLEROMA && gte(v.version, '1.0.0'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
@import 'components/emoji-reacts';
|
@import 'components/emoji-reacts';
|
||||||
@import 'components/status';
|
@import 'components/status';
|
||||||
@import 'components/reply-indicator';
|
@import 'components/reply-indicator';
|
||||||
|
@import 'components/reply-mentions';
|
||||||
@import 'components/detailed-status';
|
@import 'components/detailed-status';
|
||||||
@import 'components/list-forms';
|
@import 'components/list-forms';
|
||||||
@import 'components/media-gallery';
|
@import 'components/media-gallery';
|
||||||
|
|
|
@ -725,7 +725,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-modal {
|
.compose-modal,
|
||||||
|
.reply-mentions-modal {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
@ -755,6 +756,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 895px) {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.compose-modal {
|
||||||
&__close {
|
&__close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
@ -808,12 +818,27 @@
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 895px) {
|
.reply-mentions-modal {
|
||||||
margin: 0;
|
&__back {
|
||||||
border-radius: 0;
|
position: absolute;
|
||||||
height: 100vh;
|
left: 10px;
|
||||||
width: 100vw;
|
left: max(10px, env(safe-area-inset-right));
|
||||||
|
color: var(--primary-text-color--faint);
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accounts {
|
||||||
|
display: block;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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