Merge commit 'eaa1f9e450
' into kb_migration
This commit is contained in:
commit
2a813d517d
73 changed files with 987 additions and 72 deletions
|
@ -165,8 +165,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
},
|
||||
|
||||
onAddToAntenna (account) {
|
||||
dispatch(openModal('ANTENNA_ADDER', {
|
||||
accountId: account.get('id'),
|
||||
dispatch(openModal({
|
||||
modalType: 'ANTENNA_ADDER',
|
||||
modalProps: {
|
||||
accountId: account.get('id'),
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
|
|
|
@ -152,6 +152,17 @@ export default class ColumnSettings extends PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-status_reference'>
|
||||
<span id='notifications-status_reference' className='column-settings__section'><FormattedMessage id='notifications.column_settings.status_reference' defaultMessage='References:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status_reference']} onChange={onChange} label={alertStr} />
|
||||
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'status_reference']} onChange={this.onPushChange} label={pushStr} />}
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'status_reference']} onChange={onChange} label={showStr} />
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'status_reference']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-poll'>
|
||||
<span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ const tooltips = defineMessages({
|
|||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
|
||||
emojiReactions: { id: 'notifications.filter.emoji_reactions', defaultMessage: 'Stamps' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
status_references: { id: 'notifications.filter.status_references', defaultMessage: 'Status references' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
|
||||
|
@ -90,6 +91,13 @@ class FilterBar extends PureComponent {
|
|||
>
|
||||
<Icon id='retweet' fixedWidth />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'status_reference' ? 'active' : ''}
|
||||
onClick={this.onClick('status_reference')}
|
||||
title={intl.formatMessage(tooltips.status_references)}
|
||||
>
|
||||
<Icon id='link' fixedWidth />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'poll' ? 'active' : ''}
|
||||
onClick={this.onClick('poll')}
|
||||
|
|
|
@ -28,6 +28,7 @@ const messages = defineMessages({
|
|||
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
|
||||
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
|
||||
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
|
||||
statusReference: { id: 'notification.status_reference', defaultMessage: '{name} refered' },
|
||||
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
|
||||
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
|
||||
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
|
||||
|
@ -288,6 +289,40 @@ class Notification extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
renderStatusReference (notification, link) {
|
||||
const { intl, unread } = this.props;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={this.getHandlers()}>
|
||||
<div className={classNames('notification notification-status_reference focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.statusReference, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
|
||||
<div className='notification__message'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<Icon id='link' fixedWidth />
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.status_reference' defaultMessage='{name} referenced your status' values={{ name: link }} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<StatusContainer
|
||||
id={notification.get('status')}
|
||||
withDismiss
|
||||
hidden={this.props.hidden}
|
||||
onMoveDown={this.handleMoveDown}
|
||||
onMoveUp={this.handleMoveUp}
|
||||
contextType='notifications'
|
||||
getScrollPosition={this.props.getScrollPosition}
|
||||
updateScrollBottom={this.props.updateScrollBottom}
|
||||
cachedMediaWidth={this.props.cachedMediaWidth}
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
unread={this.props.unread}
|
||||
/>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
renderStatus (notification, link) {
|
||||
const { intl, unread, status } = this.props;
|
||||
|
||||
|
@ -479,6 +514,8 @@ class Notification extends ImmutablePureComponent {
|
|||
return this.renderEmojiReaction(notification, link);
|
||||
case 'reblog':
|
||||
return this.renderReblog(notification, link);
|
||||
case 'status_reference':
|
||||
return this.renderStatusReference(notification, link);
|
||||
case 'status':
|
||||
return this.renderStatus(notification, link);
|
||||
case 'update':
|
||||
|
|
|
@ -42,6 +42,7 @@ const messages = defineMessages({
|
|||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
||||
reference: { id: 'status.reference', defaultMessage: 'Add reference' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
|
@ -69,6 +70,7 @@ class ActionBar extends PureComponent {
|
|||
onReblogForceModal: PropTypes.func.isRequired,
|
||||
onFavourite: PropTypes.func.isRequired,
|
||||
onEmojiReact: PropTypes.func.isRequired,
|
||||
onReference: PropTypes.func.isRequired,
|
||||
onBookmark: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
|
@ -190,6 +192,10 @@ class ActionBar extends PureComponent {
|
|||
navigator.clipboard.writeText(url);
|
||||
};
|
||||
|
||||
handleReference = () => {
|
||||
this.props.onReference(this.props.status);
|
||||
};
|
||||
|
||||
handleEmojiPick = (data) => {
|
||||
this.props.onEmojiReact(this.props.status, data);
|
||||
};
|
||||
|
@ -227,6 +233,11 @@ class ActionBar extends PureComponent {
|
|||
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.reblog), action: this.handleReblogForceModalClick });
|
||||
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
|
|
|
@ -137,6 +137,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
let reblogIcon = 'retweet';
|
||||
let favouriteLink = '';
|
||||
let emojiReactionsLink = '';
|
||||
let statusReferencesLink = '';
|
||||
let edited = '';
|
||||
|
||||
if (this.props.measureHeight) {
|
||||
|
@ -310,6 +311,26 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.context.router) {
|
||||
statusReferencesLink = (
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/references`} className='detailed-status__link'>
|
||||
<Icon id='link' />
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('status_referred_by_count')} />
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
statusReferencesLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=references`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<Icon id='link' />
|
||||
<span className='detailed-status__favorites'>
|
||||
<AnimatedNumber value={status.get('status_referred_by_count')} />
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (status.get('edited_at')) {
|
||||
edited = (
|
||||
<>
|
||||
|
@ -347,7 +368,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
<div className='detailed-status__meta'>
|
||||
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
|
||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||
</a>{edited}{visibilityLink}{searchabilityLink}{applicationLink}{reblogLink} · {favouriteLink} · {emojiReactionsLink}
|
||||
</a>{edited}{visibilityLink}{searchabilityLink}{applicationLink}{reblogLink} · {favouriteLink} · {emojiReactionsLink} - {statusReferencesLink}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
replyCompose,
|
||||
mentionCompose,
|
||||
directCompose,
|
||||
insertReferenceCompose,
|
||||
} from '../../actions/compose';
|
||||
import {
|
||||
blockDomain,
|
||||
|
@ -88,6 +89,12 @@ const makeMapStateToProps = () => {
|
|||
const getStatus = makeGetStatus();
|
||||
const getPictureInPicture = makeGetPictureInPicture();
|
||||
|
||||
const getReferenceIds = createSelector([
|
||||
(state, { id }) => state.getIn(['contexts', 'references', id]),
|
||||
], (references) => {
|
||||
return references;
|
||||
});
|
||||
|
||||
const getAncestorsIds = createSelector([
|
||||
(_, { id }) => id,
|
||||
state => state.getIn(['contexts', 'inReplyTos']),
|
||||
|
@ -147,10 +154,12 @@ const makeMapStateToProps = () => {
|
|||
|
||||
let ancestorsIds = Immutable.List();
|
||||
let descendantsIds = Immutable.List();
|
||||
let referenceIds = Immutable.List();
|
||||
|
||||
if (status) {
|
||||
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
|
||||
descendantsIds = getDescendantsIds(state, { id: status.get('id') });
|
||||
referenceIds = getReferenceIds(state, { id: status.get('id') });
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -158,6 +167,7 @@ const makeMapStateToProps = () => {
|
|||
status,
|
||||
ancestorsIds,
|
||||
descendantsIds,
|
||||
referenceIds,
|
||||
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
|
||||
domain: state.getIn(['meta', 'domain']),
|
||||
pictureInPicture: getPictureInPicture(state, { id: props.params.statusId }),
|
||||
|
@ -200,6 +210,7 @@ class Status extends ImmutablePureComponent {
|
|||
isLoading: PropTypes.bool,
|
||||
ancestorsIds: ImmutablePropTypes.list,
|
||||
descendantsIds: ImmutablePropTypes.list,
|
||||
referenceIds: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
askReplyConfirmation: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
|
@ -356,6 +367,10 @@ class Status extends ImmutablePureComponent {
|
|||
this.handleReblogClick(status, e, true);
|
||||
};
|
||||
|
||||
handleReference = (status) => {
|
||||
this.props.dispatch(insertReferenceCompose(0, status.get('url')));
|
||||
};
|
||||
|
||||
handleBookmarkClick = (status) => {
|
||||
if (status.get('bookmarked')) {
|
||||
this.props.dispatch(unbookmark(status));
|
||||
|
@ -442,8 +457,8 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleToggleAll = () => {
|
||||
const { status, ancestorsIds, descendantsIds } = this.props;
|
||||
const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
|
||||
const { status, ancestorsIds, descendantsIds, referenceIds } = this.props;
|
||||
const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS(), referenceIds.toJS());
|
||||
|
||||
if (status.get('hidden')) {
|
||||
this.props.dispatch(revealStatus(statusIds));
|
||||
|
@ -636,8 +651,8 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
let ancestors, descendants;
|
||||
const { isLoading, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props;
|
||||
let ancestors, descendants, references;
|
||||
const { isLoading, status, ancestorsIds, descendantsIds, referenceIds, intl, domain, multiColumn, pictureInPicture } = this.props;
|
||||
const { fullscreen } = this.state;
|
||||
|
||||
if (isLoading) {
|
||||
|
@ -654,6 +669,10 @@ class Status extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (referenceIds && referenceIds.size > 0) {
|
||||
references = <>{this.renderChildren(referenceIds, true)}</>;
|
||||
}
|
||||
|
||||
if (ancestorsIds && ancestorsIds.size > 0) {
|
||||
ancestors = <>{this.renderChildren(ancestorsIds, true)}</>;
|
||||
}
|
||||
|
@ -690,6 +709,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
<ScrollContainer scrollKey='thread'>
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
|
||||
{references}
|
||||
{ancestors}
|
||||
|
||||
<HotKeys handlers={handlers}>
|
||||
|
@ -717,6 +737,7 @@ class Status extends ImmutablePureComponent {
|
|||
onEmojiReact={this.handleEmojiReact}
|
||||
onReblog={this.handleReblogClick}
|
||||
onReblogForceModal={this.handleReblogForceModalClick}
|
||||
onReference={this.handleReference}
|
||||
onBookmark={this.handleBookmarkClick}
|
||||
onDelete={this.handleDeleteClick}
|
||||
onEdit={this.handleEditClick}
|
||||
|
|
95
app/javascript/mastodon/features/status_references/index.jsx
Normal file
95
app/javascript/mastodon/features/status_references/index.jsx
Normal file
|
@ -0,0 +1,95 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { fetchStatusReferences } from 'mastodon/actions/interactions';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import StatusContainer from 'mastodon/containers/status_container';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'referred_by', props.params.statusId]),
|
||||
});
|
||||
|
||||
class StatusReferences extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
UNSAFE_componentWillMount () {
|
||||
if (!this.props.accountIds) {
|
||||
this.props.dispatch(fetchStatusReferences(this.props.params.statusId));
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||
this.props.dispatch(fetchStatusReferences(nextProps.params.statusId));
|
||||
}
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.props.dispatch(fetchStatusReferences(this.props.params.statusId));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, accountIds, multiColumn } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.status_references' defaultMessage='No one has referred this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn}>
|
||||
<ColumnHeader
|
||||
showBackButton
|
||||
multiColumn={multiColumn}
|
||||
extraButton={(
|
||||
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='references'
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{accountIds.map(id =>
|
||||
<StatusContainer key={id} id={id} withNote={false} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(StatusReferences));
|
|
@ -45,6 +45,7 @@ import {
|
|||
Reblogs,
|
||||
Favourites,
|
||||
EmojiReactions,
|
||||
StatusReferences,
|
||||
DirectTimeline,
|
||||
HashtagTimeline,
|
||||
Notifications,
|
||||
|
@ -223,6 +224,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
<WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/emoji_reactions' component={EmojiReactions} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/references' component={StatusReferences} content={children} />
|
||||
|
||||
{/* Legacy routes, cannot be easily factored with other routes because they share a param name */}
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
|
@ -231,6 +233,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/emoji_reactions' component={EmojiReactions} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/references' component={StatusReferences} content={children} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
||||
|
|
|
@ -86,6 +86,10 @@ export function EmojiReactions () {
|
|||
return import(/* webpackChunkName: "features/emoji_reactions" */'../../emoji_reactions');
|
||||
}
|
||||
|
||||
export function StatusReferences () {
|
||||
return import(/* webpackChunkName: "features/status_references" */'../../status_references');
|
||||
}
|
||||
|
||||
export function FollowRequests () {
|
||||
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests');
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue