Add status references list page

This commit is contained in:
KMY 2023-07-06 15:34:09 +09:00
parent 9d9a85c770
commit c656f41b35
10 changed files with 239 additions and 5 deletions

View file

@ -96,7 +96,7 @@ class FilterBar extends PureComponent {
onClick={this.onClick('status_reference')}
title={intl.formatMessage(tooltips.status_references)}
>
<Icon id='retweet' fixedWidth />
<Icon id='link' fixedWidth />
</button>
<button
className={selectedFilter === 'poll' ? 'active' : ''}

View file

@ -294,10 +294,10 @@ class Notification extends ImmutablePureComponent {
return (
<HotKeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-reblog focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<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='retweet' fixedWidth />
<Icon id='link' fixedWidth />
</div>
<span title={notification.get('created_at')}>

View file

@ -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>

View 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));

View file

@ -46,6 +46,7 @@ import {
Reblogs,
Favourites,
EmojiReactions,
StatusReferences,
DirectTimeline,
HashtagTimeline,
Notifications,
@ -221,6 +222,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} />
@ -229,6 +231,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} />

View file

@ -82,6 +82,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');
}