From c656f41b3547273b10e0989a304c55329a549e7f Mon Sep 17 00:00:00 2001 From: KMY Date: Thu, 6 Jul 2023 15:34:09 +0900 Subject: [PATCH] Add status references list page --- .../referred_by_statuses_controller.rb | 67 +++++++++++++ .../mastodon/actions/interactions.js | 41 +++++++- .../notifications/components/filter_bar.jsx | 2 +- .../notifications/components/notification.jsx | 4 +- .../status/components/detailed_status.jsx | 23 ++++- .../features/status_references/index.jsx | 95 +++++++++++++++++++ app/javascript/mastodon/features/ui/index.jsx | 3 + .../features/ui/util/async-components.js | 4 + .../mastodon/reducers/user_lists.js | 4 + config/routes/api.rb | 1 + 10 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 app/controllers/api/v1/statuses/referred_by_statuses_controller.rb create mode 100644 app/javascript/mastodon/features/status_references/index.jsx diff --git a/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb b/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb new file mode 100644 index 0000000000..7f3392c260 --- /dev/null +++ b/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController + include Authorization + + before_action -> { authorize_if_got_token! :read, :'read:accounts' } + before_action :set_status + after_action :insert_pagination_headers + + def index + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + end + + private + + def load_statuses + cached_references + end + + def cached_references + cache_collection(results, Status) + end + + def results + @results ||= Status.where(id: @status.referenced_by_status_objects.select(:status_id)).paginate_by_max_id( + limit_param(DEFAULT_STATUSES_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_referred_by_index_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_status_referred_by_index_url pagination_params(min_id: pagination_since_id) unless results.empty? + end + + def pagination_max_id + results.last.id + end + + def pagination_since_id + results.first.id + end + + def records_continue? + results.size == limit_param(DEFAULT_STATUSES_LIMIT) + end + + def set_status + @status = Status.find(params[:status_id]) + authorize @status, :show? + rescue Mastodon::NotPermittedError + not_found + end + + def pagination_params(core_params) + params.slice(:limit).permit(:limit).merge(core_params) + end +end diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index ee27c7e0d5..e7841e178a 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -1,6 +1,6 @@ import api from '../api'; -import { importFetchedAccounts, importFetchedStatus } from './importer'; +import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer'; export const REBLOG_REQUEST = 'REBLOG_REQUEST'; export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; @@ -34,6 +34,10 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST'; export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; +export const STATUS_REFERENCES_FETCH_REQUEST = 'STATUS_REFERENCES_FETCH_REQUEST'; +export const STATUS_REFERENCES_FETCH_SUCCESS = 'STATUS_REFERENCES_FETCH_SUCCESS'; +export const STATUS_REFERENCES_FETCH_FAIL = 'STATUS_REFERENCES_FETCH_FAIL'; + export const EMOJI_REACTIONS_FETCH_REQUEST = 'EMOJI_REACTIONS_FETCH_REQUEST'; export const EMOJI_REACTIONS_FETCH_SUCCESS = 'EMOJI_REACTIONS_FETCH_SUCCESS'; export const EMOJI_REACTIONS_FETCH_FAIL = 'EMOJI_REACTIONS_FETCH_FAIL'; @@ -470,6 +474,41 @@ export function fetchEmojiReactionsFail(id, error) { }; } +export function fetchStatusReferences(id) { + return (dispatch, getState) => { + dispatch(fetchStatusReferencesRequest(id)); + + api(getState).get(`/api/v1/statuses/${id}/referred_by`).then(response => { + dispatch(importFetchedStatuses(response.data)); + dispatch(fetchStatusReferencesSuccess(id, response.data)); + }).catch(error => { + dispatch(fetchStatusReferencesFail(id, error)); + }); + }; +} + +export function fetchStatusReferencesRequest(id) { + return { + type: STATUS_REFERENCES_FETCH_REQUEST, + id, + }; +} + +export function fetchStatusReferencesSuccess(id, statuses) { + return { + type: STATUS_REFERENCES_FETCH_SUCCESS, + id, + statuses, + }; +} + +export function fetchStatusReferencesFail(id, error) { + return { + type: STATUS_REFERENCES_FETCH_FAIL, + error, + }; +} + export function pin(status) { return (dispatch, getState) => { dispatch(pinRequest(status)); diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.jsx b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx index 7fcd63e110..2c0431d3fd 100644 --- a/app/javascript/mastodon/features/notifications/components/filter_bar.jsx +++ b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx @@ -96,7 +96,7 @@ class FilterBar extends PureComponent { onClick={this.onClick('status_reference')} title={intl.formatMessage(tooltips.status_references)} > - + + )} + /> + + + {accountIds.map(id => + , + )} + + + + + + + ); + } + +} + +export default connect(mapStateToProps)(injectIntl(StatusReferences)); diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index b765ee568b..73ba5c1a42 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -46,6 +46,7 @@ import { Reblogs, Favourites, EmojiReactions, + StatusReferences, DirectTimeline, HashtagTimeline, Notifications, @@ -221,6 +222,7 @@ class SwitchingColumnsArea extends PureComponent { + {/* Legacy routes, cannot be easily factored with other routes because they share a param name */} @@ -229,6 +231,7 @@ class SwitchingColumnsArea extends PureComponent { + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index a10991026a..be6027464c 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -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'); } diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index e731b619c0..99d91dbc77 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -48,6 +48,7 @@ import { REBLOGS_FETCH_SUCCESS, FAVOURITES_FETCH_SUCCESS, EMOJI_REACTIONS_FETCH_SUCCESS, + STATUS_REFERENCES_FETCH_SUCCESS, } from '../actions/interactions'; import { MUTES_FETCH_REQUEST, @@ -75,6 +76,7 @@ const initialState = ImmutableMap({ reblogged_by: initialListState, favourited_by: initialListState, emoji_reactioned_by: initialListState, + referred_by: initialListState, follow_requests: initialListState, blocks: initialListState, mutes: initialListState, @@ -141,6 +143,8 @@ export default function userLists(state = initialState, action) { return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id))); case EMOJI_REACTIONS_FETCH_SUCCESS: return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts)); + case STATUS_REFERENCES_FETCH_SUCCESS: + return state.setIn(['referred_by', action.id], ImmutableList(action.statuses.map(item => item.id))); case NOTIFICATIONS_UPDATE: return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state; case FOLLOW_REQUESTS_FETCH_SUCCESS: diff --git a/config/routes/api.rb b/config/routes/api.rb index 57543be8fb..9f01253d8c 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -12,6 +12,7 @@ namespace :api, format: false do resources :favourited_by, controller: :favourited_by_accounts, only: :index resources :emoji_reactioned_by, controller: :emoji_reactioned_by_accounts, only: :index resources :emoji_reactioned_by_slim, controller: :emoji_reactioned_by_accounts_slim, only: :index + resources :referred_by, controller: :referred_by_statuses, only: :index resource :reblog, only: :create post :unreblog, to: 'reblogs#destroy'