Add quoted_update notification type (#35820)

This commit is contained in:
Claire 2025-08-25 17:44:18 +02:00 committed by GitHub
parent 7aba79ade9
commit f3a932d8a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 117 additions and 6 deletions

View File

@ -31,7 +31,8 @@ export type NotificationWithStatusType =
| 'mention'
| 'quote'
| 'poll'
| 'update';
| 'update'
| 'quoted_update';
export type NotificationType =
| NotificationWithStatusType

View File

@ -38,6 +38,7 @@ const messages = defineMessages({
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your post' },
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
quoted_update: { id: 'notification.quoted_update', defaultMessage: '{name} edited a post you have quoted' },
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' },
@ -336,6 +337,41 @@ class Notification extends ImmutablePureComponent {
);
}
renderQuotedUpdate (notification, link) {
const { intl, unread, status } = this.props;
if (!status) {
return null;
}
return (
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-update focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.update, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='pencil' icon={EditIcon} />
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.quoted_update' defaultMessage='{name} edited a post you have quoted' values={{ name: link }} />
</span>
</div>
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
contextType='notifications'
muted
withDismiss
hidden={this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</Hotkeys>
);
}
renderPoll (notification, account) {
const { intl, unread, status } = this.props;
const ownPoll = me === account.get('id');
@ -492,6 +528,8 @@ class Notification extends ImmutablePureComponent {
return this.renderStatus(notification, link);
case 'update':
return this.renderUpdate(notification, link);
case 'quoted_update':
return this.renderQuotedUpdate(notification, link);
case 'poll':
return this.renderPoll(notification, account);
case 'severed_relationships':

View File

@ -16,6 +16,7 @@ import { NotificationMention } from './notification_mention';
import { NotificationModerationWarning } from './notification_moderation_warning';
import { NotificationPoll } from './notification_poll';
import { NotificationQuote } from './notification_quote';
import { NotificationQuotedUpdate } from './notification_quoted_update';
import { NotificationReblog } from './notification_reblog';
import { NotificationSeveredRelationships } from './notification_severed_relationships';
import { NotificationStatus } from './notification_status';
@ -115,6 +116,14 @@ export const NotificationGroup: React.FC<{
<NotificationUpdate unread={unread} notification={notificationGroup} />
);
break;
case 'quoted_update':
content = (
<NotificationQuotedUpdate
unread={unread}
notification={notificationGroup}
/>
);
break;
case 'admin.sign_up':
content = (
<NotificationAdminSignUp

View File

@ -0,0 +1,31 @@
import { FormattedMessage } from 'react-intl';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import type { NotificationGroupQuotedUpdate } from 'mastodon/models/notification_group';
import type { LabelRenderer } from './notification_group_with_status';
import { NotificationWithStatus } from './notification_with_status';
const labelRenderer: LabelRenderer = (displayedName) => (
<FormattedMessage
id='notification.quoted_update'
defaultMessage='{name} edited a post you have quoted'
values={{ name: displayedName }}
/>
);
export const NotificationQuotedUpdate: React.FC<{
notification: NotificationGroupQuotedUpdate;
unread: boolean;
}> = ({ notification, unread }) => (
<NotificationWithStatus
type='update'
icon={EditIcon}
iconId='edit'
accountIds={notification.sampleAccountIds}
count={notification.notifications_count}
statusId={notification.statusId}
labelRenderer={labelRenderer}
unread={unread}
/>
);

View File

@ -620,6 +620,7 @@
"notification.moderation_warning.action_suspend": "Your account has been suspended.",
"notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you voted in has ended",
"notification.quoted_update": "{name} edited a post you have quoted",
"notification.reblog": "{name} boosted your post",
"notification.reblog.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> boosted your post",
"notification.relationships_severance_event": "Lost connections with {name}",

View File

@ -39,6 +39,8 @@ export type NotificationGroupMention = BaseNotificationWithStatus<'mention'>;
export type NotificationGroupQuote = BaseNotificationWithStatus<'quote'>;
export type NotificationGroupPoll = BaseNotificationWithStatus<'poll'>;
export type NotificationGroupUpdate = BaseNotificationWithStatus<'update'>;
export type NotificationGroupQuotedUpdate =
BaseNotificationWithStatus<'quoted_update'>;
export type NotificationGroupFollow = BaseNotification<'follow'>;
export type NotificationGroupFollowRequest = BaseNotification<'follow_request'>;
export type NotificationGroupAdminSignUp = BaseNotification<'admin.sign_up'>;
@ -91,6 +93,7 @@ export type NotificationGroup =
| NotificationGroupQuote
| NotificationGroupPoll
| NotificationGroupUpdate
| NotificationGroupQuotedUpdate
| NotificationGroupFollow
| NotificationGroupFollowRequest
| NotificationGroupModerationWarning
@ -141,7 +144,8 @@ export function createNotificationGroupFromJSON(
case 'mention':
case 'quote':
case 'poll':
case 'update': {
case 'update':
case 'quoted_update': {
const { status_id: statusId, ...groupWithoutStatus } = group;
return {
statusId: statusId ?? undefined,
@ -215,6 +219,7 @@ export function createNotificationGroupFromNotificationJSON(
case 'quote':
case 'poll':
case 'update':
case 'quoted_update':
return {
...group,
type: notification.type,

View File

@ -77,6 +77,9 @@ class Notification < ApplicationRecord
quote: {
filterable: true,
}.freeze,
quoted_update: {
filterable: false,
}.freeze,
}.freeze
TYPES = PROPERTIES.keys.freeze
@ -89,6 +92,7 @@ class Notification < ApplicationRecord
favourite: [favourite: :status],
poll: [poll: :status],
update: :status,
quoted_update: :status,
'admin.report': [report: :target_account],
}.freeze
@ -120,7 +124,7 @@ class Notification < ApplicationRecord
def target_status
case type
when :status, :update
when :status, :update, :quoted_update
status
when :reblog
status&.reblog
@ -172,7 +176,7 @@ class Notification < ApplicationRecord
cached_status = cached_statuses_by_id[notification.target_status.id]
case notification.type
when :status, :update
when :status, :update, :quoted_update
notification.status = cached_status
when :reblog
notification.status.reblog = cached_status
@ -202,7 +206,9 @@ class Notification < ApplicationRecord
return unless new_record?
case activity_type
when 'Status', 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report', 'Quote'
when 'Status'
self.from_account_id = type == :quoted_update ? activity&.quote&.quoted_account_id : activity&.account_id
when 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report', 'Quote'
self.from_account_id = activity&.account_id
when 'Mention'
self.from_account_id = activity&.status&.account_id

View File

@ -24,7 +24,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
end
def status_type?
[:favourite, :reblog, :status, :mention, :poll, :update, :quote].include?(object.type)
[:favourite, :reblog, :status, :mention, :poll, :update, :quote, :quoted_update].include?(object.type)
end
def report_type?

View File

@ -99,6 +99,12 @@ class FanOutOnWriteService < BaseService
[account.id, @status.id, 'Status', 'update']
end
end
@status.quotes.accepted.find_in_batches do |quotes|
LocalNotificationWorker.push_bulk(quotes) do |quote|
[quote.account_id, quote.status_id, 'Status', 'quoted_update']
end
end
end
def deliver_to_all_followers!

View File

@ -8,6 +8,7 @@ class NotifyService < BaseService
admin.report
admin.sign_up
update
quoted_update
poll
status
moderation_warning

View File

@ -17,6 +17,8 @@ class LocalNotificationWorker
# should replace the previous ones.
if type == 'update'
Notification.where(account: receiver, activity: activity, type: 'update').in_batches.delete_all
elsif type == 'quoted_update'
Notification.where(account: receiver, activity: activity, type: 'quoted_update').in_batches.delete_all
elsif Notification.where(account: receiver, activity: activity, type: type).any?
return
end

View File

@ -69,6 +69,17 @@ RSpec.describe Notification do
end
end
context 'when the notification is a quoted post update notification' do
it 'sets the notification from_account correctly' do
status = Fabricate(:status)
quote = Fabricate(:quote, quoted_status: status)
notification = Fabricate.build(:notification, activity_type: 'Status', type: 'quoted_update', activity: quote.status)
expect(notification.from_account).to eq(status.account)
end
end
context 'when activity_type is a Follow' do
it 'sets the notification from_account correctly' do
follow = Fabricate(:follow)