Skip to content

Commit

Permalink
Wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kmycode committed Sep 24, 2023
1 parent df3b3f4 commit e17bf57
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 0 deletions.
74 changes: 74 additions & 0 deletions app/controllers/api/v1/statuses/mentioned_accounts_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

class Api::V1::Statuses::MentionedAccountsController < Api::BaseController
include Authorization

before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers

def index
cache_if_unauthenticated!
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end

private

def load_accounts
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
scope.merge(paginated_mentioned_users).to_a
end

def default_accounts
Account
.without_suspended
.includes(:account_stat)
.references(:favourites)
.where(favourites: { status_id: @status.id })
end

def paginated_favourites
Favourite.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end

def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end

def next_path
api_v1_status_favourited_by_index_url pagination_params(max_id: pagination_max_id) if records_continue?
end

def prev_path
api_v1_status_favourited_by_index_url pagination_params(since_id: pagination_since_id) unless @accounts.empty?
end

def pagination_max_id
@accounts.last.favourites.last.id
end

def pagination_since_id
@accounts.first.favourites.first.id
end

def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end

def set_status
@status = Status.find(params[:status_id])
authorize @status, :show_mentioned_users?
rescue Mastodon::NotPermittedError
not_found
end

def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end
90 changes: 90 additions & 0 deletions app/javascript/mastodon/actions/interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';

export const MENTIONED_USERS_FETCH_REQUEST = 'MENTIONED_USERS_FETCH_REQUEST';
export const MENTIONED_USERS_FETCH_SUCCESS = 'MENTIONED_USERS_FETCH_SUCCESS';
export const MENTIONED_USERS_FETCH_FAIL = 'MENTIONED_USERS_FETCH_FAIL';

export const MENTIONED_USERS_EXPAND_REQUEST = 'MENTIONED_USERS_EXPAND_REQUEST';
export const MENTIONED_USERS_EXPAND_SUCCESS = 'MENTIONED_USERS_EXPAND_SUCCESS';
export const MENTIONED_USERS_EXPAND_FAIL = 'MENTIONED_USERS_EXPAND_FAIL';

export function reblog(status, visibility) {
return function (dispatch, getState) {
dispatch(reblogRequest(status));
Expand Down Expand Up @@ -735,3 +743,85 @@ export function unpinFail(status, error) {
skipLoading: true,
};
}

export function fetchMentionedUsers(id) {
return (dispatch, getState) => {
dispatch(fetchMentionedUsersRequest(id));

api(getState).get(`/api/v1/statuses/${id}/mentioned_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchMentionedUsersSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => {
dispatch(fetchMentionedUsersFail(id, error));
});
};
}

export function fetchMentionedUsersRequest(id) {
return {
type: MENTIONED_USERS_FETCH_REQUEST,
id,
};
}

export function fetchMentionedUsersSuccess(id, accounts, next) {
return {
type: MENTIONED_USERS_FETCH_SUCCESS,
id,
accounts,
next,
};
}

export function fetchMentionedUsersFail(id, error) {
return {
type: MENTIONED_USERS_FETCH_FAIL,
id,
error,
};
}

export function expandMentionedUsers(id) {
return (dispatch, getState) => {
const url = getState().getIn(['user_lists', 'mentioned_users', id, 'next']);
if (url === null) {
return;
}

dispatch(expandMentionedUsersRequest(id));

api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');

dispatch(importFetchedAccounts(response.data));
dispatch(expandMentionedUsersSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => dispatch(expandMentionedUsersFail(id, error)));
};
}

export function expandMentionedUsersRequest(id) {
return {
type: MENTIONED_USERS_EXPAND_REQUEST,
id,
};
}

export function expandMentionedUsersSuccess(id, accounts, next) {
return {
type: MENTIONED_USERS_EXPAND_SUCCESS,
id,
accounts,
next,
};
}

export function expandMentionedUsersFail(id, error) {
return {
type: MENTIONED_USERS_EXPAND_FAIL,
id,
error,
};
}
90 changes: 90 additions & 0 deletions app/javascript/mastodon/features/mentioned_users/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import PropTypes from 'prop-types';

import { 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 { debounce } from 'lodash';

import { fetchMentionedUsers, expandMentionedUsers } from 'mastodon/actions/interactions';
import ColumnHeader from 'mastodon/components/column_header';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import AccountContainer from 'mastodon/containers/account_container';
import Column from 'mastodon/features/ui/components/column';

const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'mentioned_users', props.params.statusId, 'items']),
hasMore: !!state.getIn(['user_lists', 'mentioned_users', props.params.statusId, 'next']),
isLoading: state.getIn(['user_lists', 'mentioned_users', props.params.statusId, 'isLoading'], true),
});

class MentionedUsers extends ImmutablePureComponent {

static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired,
};

UNSAFE_componentWillMount () {
if (!this.props.accountIds) {
this.props.dispatch(fetchMentionedUsers(this.props.params.statusId));
}
}

handleLoadMore = debounce(() => {
this.props.dispatch(expandMentionedUsers(this.props.params.statusId));
}, 300, { leading: true });

render () {
const { accountIds, hasMore, isLoading, multiColumn } = this.props;

if (!accountIds) {
return (
<Column>
<LoadingIndicator />
</Column>
);
}

const emptyMessage = <FormattedMessage id='empty_column.mentioned_users' defaultMessage='No one has been mentioned by this post.' />;

return (
<Column bindToDocument={!multiColumn}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
/>

<ScrollableList
scrollKey='mentioned_users'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />,
)}
</ScrollableList>

<Helmet>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}

}

export default connect(mapStateToProps)(injectIntl(MentionedUsers));
2 changes: 2 additions & 0 deletions app/javascript/mastodon/features/ui/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
Favourites,
EmojiReactions,
StatusReferences,
MentionedUsers,
DirectTimeline,
HashtagTimeline,
AntennaTimeline,
Expand Down Expand Up @@ -243,6 +244,7 @@ class SwitchingColumnsArea extends PureComponent {
<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} />
<WrappedRoute path='/@:acct/:statusId/mentioned_users' component={MentionedUsers} 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} />
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/mastodon/features/ui/util/async-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ export function StatusReferences () {
return import(/* webpackChunkName: "features/status_references" */'../../status_references');
}

export function MentionedUsers () {
return import(/* webpackChunkName: "features/mentioned_users" */'../../mentioned_users');
}

export function FollowRequests () {
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests');
}
Expand Down
17 changes: 17 additions & 0 deletions app/javascript/mastodon/reducers/user_lists.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ import {
EMOJI_REACTIONS_EXPAND_SUCCESS,
EMOJI_REACTIONS_EXPAND_FAIL,
STATUS_REFERENCES_FETCH_SUCCESS,
MENTIONED_USERS_FETCH_REQUEST,
MENTIONED_USERS_FETCH_SUCCESS,
MENTIONED_USERS_FETCH_FAIL,
MENTIONED_USERS_EXPAND_REQUEST,
MENTIONED_USERS_EXPAND_SUCCESS,
MENTIONED_USERS_EXPAND_FAIL,
} from '../actions/interactions';
import {
MUTES_FETCH_REQUEST,
Expand Down Expand Up @@ -92,6 +98,7 @@ const initialState = ImmutableMap({
favourited_by: initialListState,
emoji_reactioned_by: initialListState,
referred_by: initialListState,
mentioned_users: initialListState,
follow_requests: initialListState,
blocks: initialListState,
mutes: initialListState,
Expand Down Expand Up @@ -205,6 +212,16 @@ export default function userLists(state = initialState, action) {
return appendToEmojiReactionList(state, ['emoji_reactioned_by', action.id], action.accounts, action.next);
case STATUS_REFERENCES_FETCH_SUCCESS:
return state.setIn(['referred_by', action.id], ImmutableList(action.statuses.map(item => item.id)));
case MENTIONED_USERS_FETCH_SUCCESS:
return normalizeList(state, ['mentioned_users', action.id], action.accounts, action.next);
case MENTIONED_USERS_EXPAND_SUCCESS:
return appendToList(state, ['mentioned_users', action.id], action.accounts, action.next);
case MENTIONED_USERS_FETCH_REQUEST:
case MENTIONED_USERS_EXPAND_REQUEST:
return state.setIn(['mentioned_users', action.id, 'isLoading'], true);
case MENTIONED_USERS_FETCH_FAIL:
case MENTIONED_USERS_EXPAND_FAIL:
return state.setIn(['mentioned_users', action.id, 'isLoading'], false);
case NOTIFICATIONS_UPDATE:
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS:
Expand Down
4 changes: 4 additions & 0 deletions app/policies/status_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def show?
end
end

def show_mentioned_users?
owned?
end

def reblog?
!requires_mention? && (!private? || owned?) && show? && !blocking_author?
end
Expand Down

0 comments on commit e17bf57

Please sign in to comment.