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)