diff --git a/app/javascript/mastodon/api_types/notifications.ts b/app/javascript/mastodon/api_types/notifications.ts
index 69fd17a256..b22ae59437 100644
--- a/app/javascript/mastodon/api_types/notifications.ts
+++ b/app/javascript/mastodon/api_types/notifications.ts
@@ -31,7 +31,8 @@ export type NotificationWithStatusType =
| 'mention'
| 'quote'
| 'poll'
- | 'update';
+ | 'update'
+ | 'quoted_update';
export type NotificationType =
| NotificationWithStatusType
diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx
index 501ef8f5c1..7f522b6392 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.jsx
+++ b/app/javascript/mastodon/features/notifications/components/notification.jsx
@@ -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 (
+
+
+
+ );
+ }
+
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':
diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx
index e992335cbc..d696211465 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx
@@ -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<{
);
break;
+ case 'quoted_update':
+ content = (
+
+ );
+ break;
case 'admin.sign_up':
content = (
(
+
+);
+
+export const NotificationQuotedUpdate: React.FC<{
+ notification: NotificationGroupQuotedUpdate;
+ unread: boolean;
+}> = ({ notification, unread }) => (
+
+);
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 79a01a02f2..57a45c2b64 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -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 {count, plural, one {# other} other {# others}} boosted your post",
"notification.relationships_severance_event": "Lost connections with {name}",
diff --git a/app/javascript/mastodon/models/notification_group.ts b/app/javascript/mastodon/models/notification_group.ts
index 9394cbbed8..7b88f90429 100644
--- a/app/javascript/mastodon/models/notification_group.ts
+++ b/app/javascript/mastodon/models/notification_group.ts
@@ -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,
diff --git a/app/models/notification.rb b/app/models/notification.rb
index acef474a59..8ee32798ad 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -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
diff --git a/app/serializers/rest/notification_group_serializer.rb b/app/serializers/rest/notification_group_serializer.rb
index 347659bdfe..40f7f04ede 100644
--- a/app/serializers/rest/notification_group_serializer.rb
+++ b/app/serializers/rest/notification_group_serializer.rb
@@ -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?
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 41a4e210e1..64769230b7 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -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!
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 95563698f4..fa2c728e57 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -8,6 +8,7 @@ class NotifyService < BaseService
admin.report
admin.sign_up
update
+ quoted_update
poll
status
moderation_warning
diff --git a/app/workers/local_notification_worker.rb b/app/workers/local_notification_worker.rb
index 749a54b73b..e76572984d 100644
--- a/app/workers/local_notification_worker.rb
+++ b/app/workers/local_notification_worker.rb
@@ -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
diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb
index 67c85b40f3..08427a521a 100644
--- a/spec/models/notification_spec.rb
+++ b/spec/models/notification_spec.rb
@@ -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)