mirror of
https://github.com/mastodon/mastodon.git
synced 2024-11-21 21:57:19 +00:00
WIP: Notifications for Report Notes
This commit is contained in:
parent
2526b32ad3
commit
0b1710f6ba
|
@ -19,6 +19,14 @@ module Admin
|
|||
log_action :reopen, @report
|
||||
end
|
||||
|
||||
User.those_who_can(:manage_reports).includes(:account).find_each do |u|
|
||||
# Prevent notifications to the author of the report note:
|
||||
next if @report_note.account.id == u.account.id
|
||||
|
||||
LocalNotificationWorker.perform_async(u.account_id, @report_note.id, 'ReportNote', 'admin.report_note')
|
||||
AdminMailer.with(recipient: u.account).new_report_note(@report_note).deliver_later if u.allows_report_emails?
|
||||
end
|
||||
|
||||
redirect_to after_create_redirect_path, notice: I18n.t('admin.report_notes.created_msg')
|
||||
else
|
||||
@report_notes = @report.notes.chronological.includes(:account)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import type { AccountWarningAction } from 'mastodon/models/notification_group';
|
||||
|
||||
import type { ApiAccountJSON } from './accounts';
|
||||
import type { ApiReportJSON } from './reports';
|
||||
import type { ApiReportJSON, ApiReportNoteJSON } from './reports';
|
||||
import type { ApiStatusJSON } from './statuses';
|
||||
|
||||
// See app/model/notification.rb
|
||||
|
@ -18,6 +18,7 @@ export const allNotificationTypes = [
|
|||
'update',
|
||||
'admin.sign_up',
|
||||
'admin.report',
|
||||
'admin.report_note',
|
||||
'moderation_warning',
|
||||
'severed_relationships',
|
||||
'annual_report',
|
||||
|
@ -39,6 +40,7 @@ export type NotificationType =
|
|||
| 'severed_relationships'
|
||||
| 'admin.sign_up'
|
||||
| 'admin.report'
|
||||
| 'admin.report_note'
|
||||
| 'annual_report';
|
||||
|
||||
export interface BaseNotificationJSON {
|
||||
|
@ -80,6 +82,16 @@ interface ReportNotificationJSON extends BaseNotificationJSON {
|
|||
report: ApiReportJSON;
|
||||
}
|
||||
|
||||
interface ReportNoteNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'admin.report_note';
|
||||
report_note: ApiReportNoteJSON;
|
||||
}
|
||||
|
||||
interface ReportNoteNotificationJSON extends BaseNotificationJSON {
|
||||
type: 'admin.report_note';
|
||||
report_note: ApiReportNoteJSON;
|
||||
}
|
||||
|
||||
type SimpleNotificationTypes = 'follow' | 'follow_request' | 'admin.sign_up';
|
||||
interface SimpleNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: SimpleNotificationTypes;
|
||||
|
@ -144,6 +156,7 @@ interface AnnualReportNotificationGroupJSON extends BaseNotificationGroupJSON {
|
|||
export type ApiNotificationJSON =
|
||||
| SimpleNotificationJSON
|
||||
| ReportNotificationJSON
|
||||
| ReportNoteNotificationJSON
|
||||
| AccountRelationshipSeveranceNotificationJSON
|
||||
| NotificationWithStatusJSON
|
||||
| ModerationWarningNotificationJSON;
|
||||
|
@ -151,6 +164,7 @@ export type ApiNotificationJSON =
|
|||
export type ApiNotificationGroupJSON =
|
||||
| SimpleNotificationGroupJSON
|
||||
| ReportNotificationGroupJSON
|
||||
| ReportNoteNotificationGroupJSON
|
||||
| AccountRelationshipSeveranceNotificationGroupJSON
|
||||
| NotificationGroupWithStatusJSON
|
||||
| ModerationWarningNotificationGroupJSON
|
||||
|
|
|
@ -14,3 +14,10 @@ export interface ApiReportJSON {
|
|||
rule_ids: string[];
|
||||
target_account: ApiAccountJSON;
|
||||
}
|
||||
|
||||
export interface ApiReportNoteJSON {
|
||||
id: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
report: ApiReportJSON;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import type { NotificationGroupAdminReportNote } from 'mastodon/models/notification_group';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
export const NotificationAdminReportNote: React.FC<{
|
||||
notification: NotificationGroupAdminReportNote;
|
||||
unread?: boolean;
|
||||
}> = ({ notification, notification: { reportNote }, unread }) => {
|
||||
const account = useAppSelector((state) =>
|
||||
state.accounts.get(notification.sampleAccountIds[0] ?? '0'),
|
||||
);
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
const domain = account.acct.split('@')[1];
|
||||
|
||||
const values = {
|
||||
name: <bdi>{domain ?? `@${account.acct}`}</bdi>,
|
||||
reportId: reportNote.report.id,
|
||||
};
|
||||
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id='notification.admin.report_note'
|
||||
defaultMessage='{name} added a report note to Report #{reportId}'
|
||||
values={values}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`/admin/reports/${reportNote.report.id}#report_note_${reportNote.id}`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className={classNames(
|
||||
'notification-group notification-group--link notification-group--admin-report focusable',
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon id='flag' icon={FlagIcon} />
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<div className='notification-group__main__header'>
|
||||
<div className='notification-group__main__header__label'>
|
||||
{message}
|
||||
<RelativeTimestamp timestamp={reportNote.created_at} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{reportNote.content.length > 0 && (
|
||||
<div className='notification-group__embedded-status__content'>
|
||||
“{reportNote.content}”
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
|
@ -8,6 +8,7 @@ import type { NotificationGroup as NotificationGroupModel } from 'mastodon/model
|
|||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { NotificationAdminReport } from './notification_admin_report';
|
||||
import { NotificationAdminReportNote } from './notification_admin_report_note';
|
||||
import { NotificationAdminSignUp } from './notification_admin_sign_up';
|
||||
import { NotificationAnnualReport } from './notification_annual_report';
|
||||
import { NotificationFavourite } from './notification_favourite';
|
||||
|
@ -136,6 +137,14 @@ export const NotificationGroup: React.FC<{
|
|||
/>
|
||||
);
|
||||
break;
|
||||
case 'admin.report_note':
|
||||
content = (
|
||||
<NotificationAdminReportNote
|
||||
unread={unread}
|
||||
notification={notificationGroup}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'moderation_warning':
|
||||
content = (
|
||||
<NotificationModerationWarning
|
||||
|
|
|
@ -8,7 +8,10 @@ import type {
|
|||
NotificationType,
|
||||
NotificationWithStatusType,
|
||||
} from 'mastodon/api_types/notifications';
|
||||
import type { ApiReportJSON } from 'mastodon/api_types/reports';
|
||||
import type {
|
||||
ApiReportJSON,
|
||||
ApiReportNoteJSON,
|
||||
} from 'mastodon/api_types/reports';
|
||||
|
||||
// Maximum number of avatars displayed in a notification group
|
||||
// This corresponds to the max lenght of `group.sampleAccountIds`
|
||||
|
@ -81,6 +84,14 @@ export interface NotificationGroupAdminReport
|
|||
report: Report;
|
||||
}
|
||||
|
||||
interface ReportNote extends Omit<ApiReportNoteJSON, 'report'> {
|
||||
report: Report;
|
||||
}
|
||||
export interface NotificationGroupAdminReportNote
|
||||
extends BaseNotification<'admin.report_note'> {
|
||||
reportNote: ReportNote;
|
||||
}
|
||||
|
||||
export type NotificationGroup =
|
||||
| NotificationGroupFavourite
|
||||
| NotificationGroupReblog
|
||||
|
@ -94,6 +105,7 @@ export type NotificationGroup =
|
|||
| NotificationGroupSeveredRelationships
|
||||
| NotificationGroupAdminSignUp
|
||||
| NotificationGroupAdminReport
|
||||
| NotificationGroupAdminReportNote
|
||||
| NotificationGroupAnnualReport;
|
||||
|
||||
function createReportFromJSON(reportJSON: ApiReportJSON): Report {
|
||||
|
@ -104,6 +116,16 @@ function createReportFromJSON(reportJSON: ApiReportJSON): Report {
|
|||
};
|
||||
}
|
||||
|
||||
function createReportNoteFromJSON(
|
||||
reportNoteJSON: ApiReportNoteJSON,
|
||||
): ReportNote {
|
||||
const { report, ...reportNote } = reportNoteJSON;
|
||||
return {
|
||||
report: createReportFromJSON(report),
|
||||
...reportNote,
|
||||
};
|
||||
}
|
||||
|
||||
function createAccountWarningFromJSON(
|
||||
warningJSON: ApiAccountWarningJSON,
|
||||
): AccountWarning {
|
||||
|
@ -153,6 +175,14 @@ export function createNotificationGroupFromJSON(
|
|||
...groupWithoutTargetAccount,
|
||||
};
|
||||
}
|
||||
case 'admin.report_note': {
|
||||
const { report_note, ...groupWithoutReportNote } = group;
|
||||
return {
|
||||
reportNote: createReportNoteFromJSON(report_note),
|
||||
sampleAccountIds,
|
||||
...groupWithoutReportNote,
|
||||
};
|
||||
}
|
||||
case 'severed_relationships':
|
||||
return {
|
||||
...group,
|
||||
|
@ -207,6 +237,11 @@ export function createNotificationGroupFromNotificationJSON(
|
|||
return { ...group, statusId: notification.status?.id };
|
||||
case 'admin.report':
|
||||
return { ...group, report: createReportFromJSON(notification.report) };
|
||||
case 'admin.report_note':
|
||||
return {
|
||||
...group,
|
||||
reportNote: createReportNoteFromJSON(notification.report_note),
|
||||
};
|
||||
case 'severed_relationships':
|
||||
return {
|
||||
...group,
|
||||
|
|
|
@ -56,6 +56,7 @@ export const notificationToMap = notification => ImmutableMap({
|
|||
created_at: notification.created_at,
|
||||
status: notification.status ? notification.status.id : null,
|
||||
report: notification.report ? fromJS(notification.report) : null,
|
||||
report_note: notification.report_note ? fromJS(notification.report_note) : null,
|
||||
event: notification.event ? fromJS(notification.event) : null,
|
||||
moderation_warning: notification.moderation_warning ? fromJS(notification.moderation_warning) : null,
|
||||
});
|
||||
|
|
|
@ -41,6 +41,7 @@ const initialState = ImmutableMap({
|
|||
update: false,
|
||||
'admin.sign_up': false,
|
||||
'admin.report': false,
|
||||
'admin.report_note': false,
|
||||
}),
|
||||
|
||||
quickFilter: ImmutableMap({
|
||||
|
@ -64,6 +65,7 @@ const initialState = ImmutableMap({
|
|||
update: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true,
|
||||
'admin.report_note': true,
|
||||
}),
|
||||
|
||||
sounds: ImmutableMap({
|
||||
|
@ -77,6 +79,7 @@ const initialState = ImmutableMap({
|
|||
update: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true,
|
||||
'admin.report_note': true,
|
||||
}),
|
||||
|
||||
group: ImmutableMap({
|
||||
|
|
|
@ -21,6 +21,14 @@ class AdminMailer < ApplicationMailer
|
|||
end
|
||||
end
|
||||
|
||||
def new_report_note(report_note)
|
||||
@report_note = report_note
|
||||
|
||||
locale_for_account(@me) do
|
||||
mail subject: default_i18n_subject(instance: @instance, id: @report_note.report.id)
|
||||
end
|
||||
end
|
||||
|
||||
def new_appeal(appeal)
|
||||
@appeal = appeal
|
||||
|
||||
|
|
|
@ -73,6 +73,9 @@ class Notification < ApplicationRecord
|
|||
'admin.report': {
|
||||
filterable: false,
|
||||
}.freeze,
|
||||
'admin.report_note': {
|
||||
filterable: false,
|
||||
}.freeze,
|
||||
}.freeze
|
||||
|
||||
TYPES = PROPERTIES.keys.freeze
|
||||
|
@ -85,6 +88,7 @@ class Notification < ApplicationRecord
|
|||
poll: [poll: :status],
|
||||
update: :status,
|
||||
'admin.report': [report: :target_account],
|
||||
'admin.report_note': [report_note: :report],
|
||||
}.freeze
|
||||
|
||||
belongs_to :account, optional: true
|
||||
|
@ -99,6 +103,7 @@ class Notification < ApplicationRecord
|
|||
belongs_to :favourite, inverse_of: :notification
|
||||
belongs_to :poll, inverse_of: false
|
||||
belongs_to :report, inverse_of: false
|
||||
belongs_to :report_note, inverse_of: false
|
||||
belongs_to :account_relationship_severance_event, inverse_of: false
|
||||
belongs_to :account_warning, inverse_of: false
|
||||
belongs_to :generated_annual_report, inverse_of: false
|
||||
|
@ -192,7 +197,7 @@ class Notification < ApplicationRecord
|
|||
return unless new_record?
|
||||
|
||||
case activity_type
|
||||
when 'Status', 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report'
|
||||
when 'Status', 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report', 'ReportNote'
|
||||
self.from_account_id = activity&.account_id
|
||||
when 'Mention'
|
||||
self.from_account_id = activity&.status&.account_id
|
||||
|
|
|
@ -49,6 +49,7 @@ class NotificationGroup < ActiveModelSerializers::Model
|
|||
delegate :type,
|
||||
:target_status,
|
||||
:report,
|
||||
:report_note,
|
||||
:account_relationship_severance_event,
|
||||
:account_warning,
|
||||
:generated_annual_report,
|
||||
|
|
|
@ -11,6 +11,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
|||
attribute :sample_account_ids
|
||||
attribute :status_id, if: :status_type?
|
||||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||
belongs_to :report_note, if: :report_note_type?, serializer: REST::ReportNoteSerializer
|
||||
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
|
||||
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
|
||||
belongs_to :generated_annual_report, key: :annual_report, if: :annual_report_event?, serializer: REST::AnnualReportEventSerializer
|
||||
|
@ -31,6 +32,10 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
|||
object.type == :'admin.report'
|
||||
end
|
||||
|
||||
def report_note_type?
|
||||
object.type == :'admin.report_note'
|
||||
end
|
||||
|
||||
def relationship_severance_event?
|
||||
object.type == :severed_relationships
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer
|
||||
belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
|
||||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||
belongs_to :report_note, if: :report_note_type?, serializer: REST::ReportNoteSerializer
|
||||
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
|
||||
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer
|
||||
|
||||
|
@ -28,6 +29,10 @@ class REST::NotificationSerializer < ActiveModel::Serializer
|
|||
object.type == :'admin.report'
|
||||
end
|
||||
|
||||
def report_note_type?
|
||||
object.type == :'admin.report_note'
|
||||
end
|
||||
|
||||
def relationship_severance_event?
|
||||
object.type == :severed_relationships
|
||||
end
|
||||
|
|
11
app/serializers/rest/report_note_serializer.rb
Normal file
11
app/serializers/rest/report_note_serializer.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::ReportNoteSerializer < ActiveModel::Serializer
|
||||
attributes :id, :content, :created_at
|
||||
|
||||
has_one :report, serializer: REST::ReportSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
end
|
|
@ -6,6 +6,7 @@ class NotifyService < BaseService
|
|||
# TODO: the severed_relationships and annual_report types probably warrants email notifications
|
||||
NON_EMAIL_TYPES = %i(
|
||||
admin.report
|
||||
admin.report_note
|
||||
admin.sign_up
|
||||
update
|
||||
poll
|
||||
|
@ -23,6 +24,7 @@ class NotifyService < BaseService
|
|||
NON_FILTERABLE_TYPES = %i(
|
||||
admin.sign_up
|
||||
admin.report
|
||||
admin.report_note
|
||||
poll
|
||||
update
|
||||
account_warning
|
||||
|
|
5
app/views/admin_mailer/new_report_note.text.erb
Normal file
5
app/views/admin_mailer/new_report_note.text.erb
Normal file
|
@ -0,0 +1,5 @@
|
|||
<%= raw t('admin_mailer.new_report_note.body', report_id: @report_note.report.id, account: @report_note.account.pretty_acct) %>
|
||||
|
||||
> <%= raw word_wrap(@report_note.content, break_sequence: "\n> ") %>
|
||||
|
||||
<%= raw t('application_mailer.view')%> <%= admin_report_url(@report_note.report, anchor: dom_id(@report_note)) %>
|
|
@ -1052,6 +1052,9 @@ en:
|
|||
body: "%{reporter} has reported %{target}"
|
||||
body_remote: Someone from %{domain} has reported %{target}
|
||||
subject: New report for %{instance} (#%{id})
|
||||
new_report_note:
|
||||
body: "%{account} has added a report note to Report #%{report_id}:"
|
||||
subject: "New report note for %{instance} (Report #%{id})"
|
||||
new_software_updates:
|
||||
body: New Mastodon versions have been released, you may want to update!
|
||||
subject: New Mastodon versions are available for %{instance}!
|
||||
|
|
Loading…
Reference in a new issue