Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master

This commit is contained in:
Jenkins 2018-04-11 00:50:09 +00:00
commit 33c2bbdda7
40 changed files with 488 additions and 110 deletions

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Admin
class ChangeEmailsController < BaseController
before_action :set_account
before_action :require_local_account!
def show
authorize @user, :change_email?
end
def update
authorize @user, :change_email?
new_email = resource_params.fetch(:unconfirmed_email)
if new_email != @user.email
@user.update!(
unconfirmed_email: new_email,
# Regenerate the confirmation token:
confirmation_token: nil
)
log_action :change_email, @user
@user.send_confirmation_instructions
end
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.change_email.changed_msg')
end
private
def set_account
@account = Account.find(params[:account_id])
@user = @account.user
end
def require_local_account!
redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present?
end
def resource_params
params.require(:user).permit(
:unconfirmed_email
)
end
end
end

View File

@ -8,19 +8,26 @@ module Admin
authorize ReportNote, :create?
@report_note = current_account.report_notes.new(resource_params)
@report = @report_note.report
if @report_note.save
if params[:create_and_resolve]
@report_note.report.update!(action_taken: true, action_taken_by_account_id: current_account.id)
log_action :resolve, @report_note.report
@report.resolve!(current_account)
log_action :resolve, @report
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
else
redirect_to admin_report_path(@report_note.report_id), notice: I18n.t('admin.report_notes.created_msg')
return
end
if params[:create_and_unresolve]
@report.unresolve!
log_action :reopen, @report
end
redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
else
@report = @report_note.report
@report_notes = @report.notes.latest
@report_history = @report.history
@form = Form::StatusBatch.new
render template: 'admin/reports/show'

View File

@ -13,6 +13,7 @@ module Admin
authorize @report, :show?
@report_note = @report.notes.new
@report_notes = @report.notes.latest
@report_history = @report.history
@form = Form::StatusBatch.new
end
@ -38,36 +39,33 @@ module Admin
@report.update!(assigned_account_id: nil)
log_action :unassigned, @report
when 'reopen'
@report.update!(action_taken: false, action_taken_by_account_id: nil)
@report.unresolve!
log_action :reopen, @report
when 'resolve'
@report.update!(action_taken_by_current_attributes)
@report.resolve!(current_account)
log_action :resolve, @report
when 'suspend'
Admin::SuspensionWorker.perform_async(@report.target_account.id)
log_action :resolve, @report
log_action :suspend, @report.target_account
resolve_all_target_account_reports
@report.reload
when 'silence'
@report.target_account.update!(silenced: true)
log_action :resolve, @report
log_action :silence, @report.target_account
resolve_all_target_account_reports
@report.reload
else
raise ActiveRecord::RecordNotFound
end
end
def action_taken_by_current_attributes
{ action_taken: true, action_taken_by_account_id: current_account.id }
@report.reload
end
def resolve_all_target_account_reports
unresolved_reports_for_target_account.update_all(
action_taken_by_current_attributes
)
unresolved_reports_for_target_account.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
end
def unresolved_reports_for_target_account

View File

@ -45,6 +45,8 @@ module Admin::ActionLogsHelper
log.recorded_changes.slice('domain', 'visible_in_picker')
elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
log.recorded_changes.slice('moderator', 'admin')
elsif log.target_type == 'User' && [:change_email].include?(log.action)
log.recorded_changes.slice('email', 'unconfirmed_email')
elsif log.target_type == 'DomainBlock'
log.recorded_changes.slice('severity', 'reject_media')
elsif log.target_type == 'Status' && log.action == :update
@ -84,7 +86,7 @@ module Admin::ActionLogsHelper
'positive'
when :create
opposite_verbs?(log) ? 'negative' : 'positive'
when :update, :reset_password, :disable_2fa, :memorialize
when :update, :reset_password, :disable_2fa, :memorialize, :change_email
'neutral'
when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen
'negative'

View File

@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
});
@injectIntl
export default class LoadGap extends React.PureComponent {
static propTypes = {
disabled: PropTypes.bool,
maxId: PropTypes.string,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleClick = () => {
this.props.onClick(this.props.maxId);
}
render () {
const { disabled, intl } = this.props;
return (
<button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}>
<i className='fa fa-ellipsis-h' />
</button>
);
}
}

View File

@ -31,6 +31,8 @@ export default class Status extends ImmutablePureComponent {
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onPin: PropTypes.func,
onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func,

View File

@ -9,6 +9,7 @@ import { me } from '../initial_state';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
@ -41,6 +42,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
onBlock: PropTypes.func,
@ -92,6 +94,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
}
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
}
@ -149,6 +155,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
} else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
menu.push(null);
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 });

View File

@ -4,28 +4,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import StatusContainer from '../containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import LoadMore from './load_more';
import LoadGap from './load_gap';
import ScrollableList from './scrollable_list';
import { FormattedMessage } from 'react-intl';
class LoadGap extends ImmutablePureComponent {
static propTypes = {
disabled: PropTypes.bool,
maxId: PropTypes.string,
onClick: PropTypes.func.isRequired,
};
handleClick = () => {
this.props.onClick(this.props.maxId);
}
render () {
return <LoadMore onClick={this.handleClick} disabled={this.props.disabled} />;
}
}
export default class StatusList extends ImmutablePureComponent {
static propTypes = {

View File

@ -5,6 +5,7 @@ import { makeGetStatus } from '../selectors';
import {
replyCompose,
mentionCompose,
directCompose,
} from '../actions/compose';
import {
reblog,
@ -102,6 +103,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
onDirect (account, router) {
dispatch(directCompose(account, router));
},
onMention (account, router) {
dispatch(mentionCompose(account, router));
},

View File

@ -13,7 +13,7 @@ import { createSelector } from 'reselect';
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
import ScrollableList from '../../components/scrollable_list';
import LoadMore from '../../components/load_more';
import LoadGap from '../../components/load_gap';
const messages = defineMessages({
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
@ -24,24 +24,6 @@ const getNotifications = createSelector([
state => state.getIn(['notifications', 'items']),
], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type'))));
class LoadGap extends React.PureComponent {
static propTypes = {
disabled: PropTypes.bool,
maxId: PropTypes.string,
onClick: PropTypes.func.isRequired,
};
handleClick = () => {
this.props.onClick(this.props.maxId);
}
render () {
return <LoadMore onClick={this.handleClick} disabled={this.props.disabled} />;
}
}
const mapStateToProps = state => ({
notifications: getNotifications(state),
isLoading: state.getIn(['notifications', 'isLoading'], true),

View File

@ -8,6 +8,7 @@ import { me } from '../../../initial_state';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
@ -37,6 +38,7 @@ export default class ActionBar extends React.PureComponent {
onReblog: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onMute: PropTypes.func,
onMuteConversation: PropTypes.func,
@ -63,6 +65,10 @@ export default class ActionBar extends React.PureComponent {
this.props.onDelete(this.props.status);
}
handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
}
handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
@ -108,6 +114,7 @@ export default class ActionBar extends React.PureComponent {
if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
menu.push(null);
}
if (me === status.getIn(['account', 'id'])) {
@ -121,6 +128,7 @@ export default class ActionBar extends React.PureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
} else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
menu.push(null);
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 });

View File

@ -19,6 +19,7 @@ import {
import {
replyCompose,
mentionCompose,
directCompose,
} from '../../actions/compose';
import { blockAccount } from '../../actions/accounts';
import {
@ -148,6 +149,10 @@ export default class Status extends ImmutablePureComponent {
}
}
handleDirectClick = (account, router) => {
this.props.dispatch(directCompose(account, router));
}
handleMentionClick = (account, router) => {
this.props.dispatch(mentionCompose(account, router));
}
@ -379,6 +384,7 @@ export default class Status extends ImmutablePureComponent {
onFavourite={this.handleFavouriteClick}
onReblog={this.handleReblogClick}
onDelete={this.handleDeleteClick}
onDirect={this.handleDirectClick}
onMention={this.handleMentionClick}
onMute={this.handleMuteClick}
onMuteConversation={this.handleConversationMuteClick}

View File

@ -197,6 +197,10 @@
"defaultMessage": "Delete",
"id": "status.delete"
},
{
"defaultMessage": "Direct message @{name}",
"id": "status.direct"
},
{
"defaultMessage": "Mention @{name}",
"id": "status.mention"
@ -1370,6 +1374,10 @@
"defaultMessage": "Delete",
"id": "status.delete"
},
{
"defaultMessage": "Direct message @{name}",
"id": "status.direct"
},
{
"defaultMessage": "Mention @{name}",
"id": "status.mention"

View File

@ -247,6 +247,7 @@
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Delete",
"status.direct": "Direct message @{name}",
"status.embed": "Embed",
"status.favourite": "Favourite",
"status.load_more": "Load more",

View File

@ -276,6 +276,7 @@
"tabs_bar.home": "Strona główna",
"tabs_bar.local_timeline": "Lokalne",
"tabs_bar.notifications": "Powiadomienia",
"tabs_bar.search": "Szukaj",
"ui.beforeunload": "Utracisz tworzony wpis, jeżeli opuścisz Mastodona.",
"upload_area.title": "Przeciągnij i upuść aby wysłać",
"upload_button.label": "Dodaj zawartość multimedialną",

View File

@ -259,16 +259,18 @@ export default function compose(state = initialState, action) {
case COMPOSE_UPLOAD_PROGRESS:
return state.set('progress', Math.round((action.loaded / action.total) * 100));
case COMPOSE_MENTION:
return state
.update('text', text => `${text}@${action.account.get('acct')} `)
.set('focusDate', new Date())
.set('idempotencyKey', uuid());
return state.withMutations(map => {
map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
map.set('focusDate', new Date());
map.set('idempotencyKey', uuid());
});
case COMPOSE_DIRECT:
return state
.update('text', text => `@${action.account.get('acct')} `)
.set('privacy', 'direct')
.set('focusDate', new Date())
.set('idempotencyKey', uuid());
return state.withMutations(map => {
map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
map.set('privacy', 'direct');
map.set('focusDate', new Date());
map.set('idempotencyKey', uuid());
});
case COMPOSE_SUGGESTIONS_CLEAR:
return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
case COMPOSE_SUGGESTIONS_READY:

View File

@ -145,6 +145,11 @@
border: 0;
background: transparent;
border-bottom: 1px solid $ui-base-color;
&.section-break {
margin: 30px 0;
border-bottom: 2px solid $ui-base-lighter-color;
}
}
.muted-hint {
@ -330,6 +335,36 @@
}
}
.report-note__comment {
margin-bottom: 20px;
}
.report-note__form {
margin-bottom: 20px;
.report-note__textarea {
box-sizing: border-box;
border: 0;
padding: 7px 4px;
margin-bottom: 10px;
font-size: 16px;
color: $ui-base-color;
display: block;
width: 100%;
outline: 0;
font-family: inherit;
resize: vertical;
}
.report-note__buttons {
text-align: right;
}
.report-note__button {
margin: 0 0 5px 5px;
}
}
.batch-form-box {
display: flex;
flex-wrap: wrap;

View File

@ -2455,6 +2455,10 @@ a.status-card {
}
}
.load-gap {
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
.regeneration-indicator {
text-align: center;
font-size: 16px;

View File

@ -126,6 +126,7 @@ class Account < ApplicationRecord
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
delegate :email,
:unconfirmed_email,
:current_sign_in_ip,
:current_sign_in_at,
:confirmed?,

View File

@ -35,6 +35,11 @@ class Admin::ActionLog < ApplicationRecord
self.recorded_changes = target.attributes
when :update, :promote, :demote
self.recorded_changes = target.previous_changes
when :change_email
self.recorded_changes = ActiveSupport::HashWithIndifferentAccess.new(
email: [target.email, nil],
unconfirmed_email: [nil, target.unconfirmed_email]
)
end
end
end

View File

@ -15,16 +15,12 @@ module StatusThreadingConcern
def ancestor_ids
Rails.cache.fetch("ancestors:#{id}") do
ancestors_without_self.pluck(:id)
ancestor_statuses.pluck(:id)
end
end
def ancestors_without_self
ancestor_statuses - [self]
end
def ancestor_statuses
Status.find_by_sql([<<-SQL.squish, id: id])
Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id])
WITH RECURSIVE search_tree(id, in_reply_to_id, path)
AS (
SELECT id, in_reply_to_id, ARRAY[id]
@ -43,11 +39,7 @@ module StatusThreadingConcern
end
def descendant_ids
descendants_without_self.pluck(:id)
end
def descendants_without_self
descendant_statuses - [self]
descendant_statuses.pluck(:id)
end
def descendant_statuses
@ -56,7 +48,7 @@ module StatusThreadingConcern
AS (
SELECT id, ARRAY[id]
FROM statuses
WHERE id = :id
WHERE in_reply_to_id = :id
UNION ALL
SELECT statuses.id, path || statuses.id
FROM search_tree

View File

@ -58,5 +58,9 @@ class CustomEmoji < ApplicationRecord
where(shortcode: shortcodes, domain: domain, disabled: false)
end
def search(shortcode)
where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%")
end
end
end

View File

@ -28,7 +28,7 @@ class CustomEmojiFilter
when 'by_domain'
CustomEmoji.where(domain: value)
when 'shortcode'
CustomEmoji.where(shortcode: value)
CustomEmoji.search(value)
else
raise "Unknown filter: #{key}"
end

View File

@ -150,8 +150,9 @@ class MediaAttachment < ApplicationRecord
'pix_fmt' => 'yuv420p',
'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
'vsync' => 'cfr',
'b:v' => '1300K',
'maxrate' => '500K',
'c:v' => 'h264',
'b:v' => '500K',
'maxrate' => '1300K',
'bufsize' => '1300K',
'crf' => 18,
},

View File

@ -39,4 +39,50 @@ class Report < ApplicationRecord
def media_attachments
MediaAttachment.where(status_id: status_ids)
end
def assign_to_self!(current_account)
update!(assigned_account_id: current_account.id)
end
def unassign!
update!(assigned_account_id: nil)
end
def resolve!(acting_account)
update!(action_taken: true, action_taken_by_account_id: acting_account.id)
end
def unresolve!
update!(action_taken: false, action_taken_by_account_id: nil)
end
def unresolved?
!action_taken?
end
def history
time_range = created_at..updated_at
sql = [
Admin::ActionLog.where(
target_type: 'Report',
target_id: id,
created_at: time_range
).unscope(:order),
Admin::ActionLog.where(
target_type: 'Account',
target_id: target_account_id,
created_at: time_range
).unscope(:order),
Admin::ActionLog.where(
target_type: 'Status',
target_id: status_ids,
created_at: time_range
).unscope(:order),
].map { |query| "(#{query.to_sql})" }.join(' UNION ALL ')
Admin::ActionLog.from("(#{sql}) AS admin_action_logs")
end
end

View File

@ -13,7 +13,7 @@
class ReportNote < ApplicationRecord
belongs_to :account
belongs_to :report, inverse_of: :notes
belongs_to :report, inverse_of: :notes, touch: true
scope :latest, -> { reorder('created_at ASC') }

View File

@ -5,6 +5,10 @@ class UserPolicy < ApplicationPolicy
staff? && !record.staff?
end
def change_email?
staff? && !record.staff?
end
def disable_2fa?
admin? && !record.staff?
end

View File

@ -28,7 +28,7 @@ class PostStatusService < BaseService
status = account.statuses.create!(text: text,
media_attachments: media || [],
thread: in_reply_to,
sensitive: options[:sensitive],
sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]),
spoiler_text: options[:spoiler_text] || '',
visibility: options[:visibility] || account.user&.setting_default_privacy,
language: LanguageDetector.instance.detect(text, account),

View File

@ -36,9 +36,13 @@
%th= t('admin.accounts.email')
%td
= @account.user_email
- if @account.user_confirmed?
= fa_icon('check')
= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
- if @account.user_unconfirmed_email.present?
%th= t('admin.accounts.unconfirmed_email')
%td
= @account.user_unconfirmed_email
%tr
%th= t('admin.accounts.login_status')
%td

View File

@ -0,0 +1,7 @@
- content_for :page_title do
= t('admin.accounts.change_email.title', username: @account.acct)
= simple_form_for @user, url: admin_account_change_email_path(@account.id) do |f|
= f.input :email, wrapper: :with_label, disabled: true, label: t('admin.accounts.change_email.current_email')
= f.input :unconfirmed_email, wrapper: :with_label, label: t('admin.accounts.change_email.new_email')
= f.button :submit, class: "button", value: t('admin.accounts.change_email.submit')

View File

@ -1,11 +1,9 @@
%tr
%td
%p
%strong= report_note.account.acct
on
%li
%h4
= report_note.account.acct
%div{ style: 'float: right' }
%time.formatted{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) }
= l report_note.created_at
= table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note)
%br/
%br/
%div{ class: 'report-note__comment' }
= simple_format(h(report_note.content))

View File

@ -2,7 +2,7 @@
= t('admin.reports.report', id: @report.id)
%div{ style: 'overflow: hidden; margin-bottom: 20px' }
- if !@report.action_taken?
- if @report.unresolved?
%div{ style: 'float: right' }
= link_to t('admin.reports.silence_account'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button'
= link_to t('admin.reports.suspend_account'), admin_report_path(@report, outcome: 'suspend'), method: :put, class: 'button'
@ -14,22 +14,29 @@
.table-wrapper
%table.table.inline-table
%tbody
%tr
%th= t('admin.reports.created_at')
%td{colspan: 2}
%time.formatted{ datetime: @report.created_at.iso8601 }
%tr
%th= t('admin.reports.updated_at')
%td{colspan: 2}
%time.formatted{ datetime: @report.updated_at.iso8601 }
%tr
%th= t('admin.reports.status')
%td{colspan: 2}
%td
- if @report.action_taken?
= t('admin.reports.resolved')
= table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put
- else
= t('admin.reports.unresolved')
%td{style: "text-align: right; overflow: hidden;"}
- if @report.action_taken?
= table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put
- if !@report.action_taken_by_account.nil?
%tr
%th= t('admin.reports.action_taken_by')
%td= @report.action_taken_by_account.acct
%td{colspan: 2}
= @report.action_taken_by_account.acct
- else
%tr
%th= t('admin.reports.assigned')
@ -44,6 +51,8 @@
- if !@report.assigned_account.nil?
= table_link_to 'trash', t('admin.reports.unassign'), admin_report_path(@report, outcome: 'unassign'), method: :put
%hr{ class: "section-break"}/
.report-accounts
.report-accounts__item
%h3= t('admin.reports.reported_account')
@ -85,22 +94,28 @@
= link_to admin_report_reported_status_path(@report, status), method: :delete, class: 'icon-button trash-button', title: t('admin.reports.delete'), data: { confirm: t('admin.reports.are_you_sure') }, remote: true do
= fa_icon 'trash'
%hr/
%hr{ class: "section-break"}/
%h3= t('admin.reports.notes.label')
- if @report_notes.length > 0
.table-wrapper
%table.table
%thead
%tr
%th
%tbody
= render @report_notes
%ul
= render @report_notes
= simple_form_for @report_note, url: admin_report_notes_path do |f|
%h4= t('admin.reports.notes.new_label')
= form_for @report_note, url: admin_report_notes_path, html: { class: 'report-note__form' } do |f|