From 6f9a7bd02ca9efe5e798d8f4642f94ad4b5b88a2 Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 4 Jan 2019 13:10:43 +0100 Subject: [PATCH] Add quick links to the admin interface in the WebUI (#8545) * Allow to show a specific status in the admin interface * Let the front-end know the current account is a moderator * Add admin links to status and account menus If the current logged-in user is an admin, add quick links to the admin interface in account and toot dropdown menu. Suggestion by @ashkitten * Use @statuses.first instead of @statuses[0] --- app/controllers/admin/statuses_controller.rb | 9 +++++++ .../mastodon/components/status_action_bar.js | 9 ++++++- .../features/account/components/action_bar.js | 8 +++++- .../features/status/components/action_bar.js | 9 ++++++- app/javascript/mastodon/initial_state.js | 1 + app/serializers/initial_state_serializer.rb | 1 + app/views/admin/statuses/show.html.haml | 27 +++++++++++++++++++ config/routes.rb | 2 +- 8 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 app/views/admin/statuses/show.html.haml diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index a69f1208454..65019503469 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -22,6 +22,15 @@ module Admin @form = Form::StatusBatch.new end + def show + authorize :status, :index? + + @statuses = @account.statuses.where(id: params[:id]) + authorize @statuses.first, :show? + + @form = Form::StatusBatch.new + end + def create authorize :status, :update? diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index becd44ec0f6..0995a14904c 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -5,7 +5,7 @@ import IconButton from './icon_button'; import DropdownMenuContainer from '../containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me } from '../initial_state'; +import { me, isStaff } from '../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -30,6 +30,8 @@ const messages = defineMessages({ pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, embed: { id: 'status.embed', defaultMessage: 'Embed' }, + admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, + admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, }); const obfuscatedCount = count => { @@ -182,6 +184,11 @@ class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); + if (isStaff) { + menu.push(null); + menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); + menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); + } } if (status.get('visibility') === 'direct') { diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index e6ae1a2fd70..8ed4c917ab1 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import { NavLink } from 'react-router-dom'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { me } from '../../../initial_state'; +import { me, isStaff } from '../../../initial_state'; import { shortNumberFormat } from '../../../utils/numbers'; const messages = defineMessages({ @@ -35,6 +35,7 @@ const messages = defineMessages({ endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, + admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, }); export default @injectIntl @@ -151,6 +152,11 @@ class ActionBar extends React.PureComponent { } } + if (account.get('id') !== me && isStaff) { + menu.push(null); + menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` }); + } + return (
{extraInfo} diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 3b30d33b255..d3b725283ad 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -4,7 +4,7 @@ import IconButton from '../../../components/icon_button'; import ImmutablePropTypes from 'react-immutable-proptypes'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; -import { me } from '../../../initial_state'; +import { me, isStaff } from '../../../initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -26,6 +26,8 @@ const messages = defineMessages({ pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, embed: { id: 'status.embed', defaultMessage: 'Embed' }, + admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, + admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, }); export default @injectIntl @@ -145,6 +147,11 @@ class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); + if (isStaff) { + menu.push(null); + menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); + menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); + } } const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && ( diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index b496c57d2d1..8c2e9d2de49 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -16,5 +16,6 @@ export const invitesEnabled = getMeta('invites_enabled'); export const version = getMeta('version'); export const mascot = getMeta('mascot'); export const profile_directory = getMeta('profile_directory'); +export const isStaff = getMeta('is_staff'); export default initialState; diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index d40379d43ab..a7a3d770ce6 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -29,6 +29,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:display_media] = object.current_account.user.setting_display_media store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers store[:reduce_motion] = object.current_account.user.setting_reduce_motion + store[:is_staff] = object.current_account.user.staff? end store diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml new file mode 100644 index 00000000000..a7a392272fc --- /dev/null +++ b/app/views/admin/statuses/show.html.haml @@ -0,0 +1,27 @@ +- content_for :page_title do + = t('admin.statuses.title') + \- + = "@#{@account.acct}" + +.filters + .back-link{ style: 'flex: 1 1 auto; text-align: right' } + = link_to admin_account_path(@account.id) do + %i.fa.fa-chevron-left.fa-fw + = t('admin.statuses.back_to_account') + +%hr.spacer/ + += form_for(@form, url: admin_account_statuses_path(@account.id)) do |f| + = hidden_field_tag :page, params[:page] + = hidden_field_tag :media, params[:media] + + .batch-table + .batch-table__toolbar + %label.batch-table__toolbar__select.batch-checkbox-all + = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions + = f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + .batch-table__body + = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f } diff --git a/config/routes.rb b/config/routes.rb index 6e4da48a08b..5bdd1986ccb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -192,7 +192,7 @@ Rails.application.routes.draw do resource :change_email, only: [:show, :update] resource :reset, only: [:create] resource :action, only: [:new, :create], controller: 'account_actions' - resources :statuses, only: [:index, :create, :update, :destroy] + resources :statuses, only: [:index, :show, :create, :update, :destroy] resources :followers, only: [:index] resource :confirmation, only: [:create] do