diff --git a/Gemfile.lock b/Gemfile.lock
index fade57c3ef8..c60943a8d13 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -436,7 +436,7 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
- puma (5.6.1)
+ puma (5.6.2)
nio4r (~> 2.0)
pundit (2.1.1)
activesupport (>= 3.0.0)
diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb
index bed57fc54ca..db2512e5f5f 100644
--- a/app/controllers/api/web/push_subscriptions_controller.rb
+++ b/app/controllers/api/web/push_subscriptions_controller.rb
@@ -26,6 +26,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
mention: alerts_enabled,
poll: alerts_enabled,
status: alerts_enabled,
+ update: alerts_enabled,
},
}
@@ -61,6 +62,15 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def data_params
- @data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
+ @data_params ||= params.require(:data).permit(:policy, alerts: [
+ :follow,
+ :follow_request,
+ :favourite,
+ :reblog,
+ :mention,
+ :poll,
+ :status,
+ :update,
+ ])
end
end
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index 4b00ea632ae..40430102cf0 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -47,7 +47,6 @@ export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY';
-
export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
@@ -136,7 +135,17 @@ const excludeTypesFromSettings = state => state.getIn(['settings', 'notification
const excludeTypesFromFilter = filter => {
- const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll']);
+ const allTypes = ImmutableList([
+ 'follow',
+ 'follow_request',
+ 'favourite',
+ 'reblog',
+ 'mention',
+ 'poll',
+ 'status',
+ 'update',
+ ]);
+
return allTypes.filterNot(item => item === filter).toJS();
};
diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js
index 5a00f232e01..1661ca8f5f2 100644
--- a/app/javascript/flavours/glitch/components/status_prepend.js
+++ b/app/javascript/flavours/glitch/components/status_prepend.js
@@ -88,6 +88,14 @@ export default class StatusPrepend extends React.PureComponent {
/>
);
}
+ case 'update':
+ return (
+
+ );
}
return null;
}
@@ -115,6 +123,9 @@ export default class StatusPrepend extends React.PureComponent {
case 'status':
iconId = 'bell';
break;
+ case 'update':
+ iconId = 'pencil';
+ break;
};
return !type ? null : (
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
index 95250c6edc0..569ba4db083 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
@@ -154,6 +154,17 @@ export default class ColumnSettings extends React.PureComponent {
+
+
+
+
+
+
+ {showPushSettings &&
}
+
+
+
+
);
}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js
index e1d9fbd0af9..1cf20589820 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notification.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js
@@ -171,6 +171,28 @@ export default class Notification extends ImmutablePureComponent {
unread={this.props.unread}
/>
);
+ case 'update':
+ return (
+
+ );
default:
return null;
}
diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js
index a53d34a83d0..48587ce64e8 100644
--- a/app/javascript/flavours/glitch/reducers/settings.js
+++ b/app/javascript/flavours/glitch/reducers/settings.js
@@ -40,6 +40,7 @@ const initialState = ImmutableMap({
mention: false,
poll: false,
status: false,
+ update: false,
}),
quickFilter: ImmutableMap({
@@ -59,6 +60,7 @@ const initialState = ImmutableMap({
mention: true,
poll: true,
status: true,
+ update: true,
}),
sounds: ImmutableMap({
@@ -69,6 +71,7 @@ const initialState = ImmutableMap({
mention: true,
poll: true,
status: true,
+ update: true,
}),
}),
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index d28470747da..66ce92ce2c5 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -1203,6 +1203,10 @@ a.sparkline {
}
}
}
+
+ @media screen and (max-width: 930px) {
+ grid-template-columns: minmax(0, 1fr);
+ }
}
.account-card {
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 663cf21e341..9370811e0c1 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -34,7 +34,6 @@ export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT';
export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
-
export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
@@ -124,7 +123,17 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
const excludeTypesFromFilter = filter => {
- const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll']);
+ const allTypes = ImmutableList([
+ 'follow',
+ 'follow_request',
+ 'favourite',
+ 'reblog',
+ 'mention',
+ 'poll',
+ 'status',
+ 'update',
+ ]);
+
return allTypes.filterNot(item => item === filter).toJS();
};
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index 005f5afda94..ada8b6e4a83 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -153,6 +153,17 @@ export default class ColumnSettings extends React.PureComponent {
+
+
+
+
+
+
+ {showPushSettings && }
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index f9f8a87f2db..cd471852b55 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -19,6 +19,7 @@ const messages = defineMessages({
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
+ update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
});
const notificationForScreenReader = (intl, message, timestamp) => {
@@ -273,6 +274,38 @@ class Notification extends ImmutablePureComponent {
);
}
+ renderUpdate (notification, link) {
+ const { intl, unread } = this.props;
+
+ return (
+
+
+
+ );
+ }
+
renderPoll (notification, account) {
const { intl, unread } = this.props;
const ownPoll = me === account.get('id');
@@ -330,6 +363,8 @@ class Notification extends ImmutablePureComponent {
return this.renderReblog(notification, link);
case 'status':
return this.renderStatus(notification, link);
+ case 'update':
+ return this.renderUpdate(notification, link);
case 'poll':
return this.renderPoll(notification, account);
}
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index 2a89919e1f0..5146abe98a3 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -36,6 +36,7 @@ const initialState = ImmutableMap({
mention: false,
poll: false,
status: false,
+ update: false,
}),
quickFilter: ImmutableMap({
@@ -55,6 +56,7 @@ const initialState = ImmutableMap({
mention: true,
poll: true,
status: true,
+ update: true,
}),
sounds: ImmutableMap({
@@ -65,6 +67,7 @@ const initialState = ImmutableMap({
mention: true,
poll: true,
status: true,
+ update: true,
}),
}),
diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js
index 1265f3cfafb..807a1bcb942 100644
--- a/app/javascript/mastodon/service_worker/web_push_locales.js
+++ b/app/javascript/mastodon/service_worker/web_push_locales.js
@@ -20,6 +20,8 @@ filenames.forEach(filename => {
'notification.mention': full['notification.mention'] || '',
'notification.reblog': full['notification.reblog'] || '',
'notification.poll': full['notification.poll'] || '',
+ 'notification.status': full['notification.status'] || '',
+ 'notification.update': full['notification.update'] || '',
'status.show_more': full['status.show_more'] || '',
'status.reblog': full['status.reblog'] || '',
diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js
index 926c5c4d7a6..48a2be7e702 100644
--- a/app/javascript/mastodon/service_worker/web_push_notifications.js
+++ b/app/javascript/mastodon/service_worker/web_push_notifications.js
@@ -102,7 +102,7 @@ const handlePush = (event) => {
options.image = undefined;
options.actions = [actionExpand(preferred_locale)];
- } else if (notification.type === 'mention') {
+ } else if (['mention', 'status'].includes(notification.type)) {
options.actions = [actionReblog(preferred_locale), actionFavourite(preferred_locale)];
}
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index d28470747da..66ce92ce2c5 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -1203,6 +1203,10 @@ a.sparkline {
}
}
}
+
+ @media screen and (max-width: 930px) {
+ grid-template-columns: minmax(0, 1fr);
+ }
}
.account-card {
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 3bf9dd483e6..c14eb8a7eb7 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -35,6 +35,7 @@ class Notification < ApplicationRecord
follow_request
favourite
poll
+ update
).freeze
TARGET_STATUS_INCLUDES_BY_TYPE = {
@@ -43,6 +44,7 @@ class Notification < ApplicationRecord
mention: [mention: :status],
favourite: [favourite: :status],
poll: [poll: :status],
+ update: :status,
}.freeze
belongs_to :account, optional: true
@@ -76,7 +78,7 @@ class Notification < ApplicationRecord
def target_status
case type
- when :status
+ when :status, :update
status
when :reblog
status&.reblog
@@ -110,7 +112,7 @@ class Notification < ApplicationRecord
cached_status = cached_statuses_by_id[notification.target_status.id]
case notification.type
- when :status
+ when :status, :update
notification.status = cached_status
when :reblog
notification.status.reblog = cached_status
diff --git a/app/models/status.rb b/app/models/status.rb
index 236f95c1f73..607b7071287 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -65,6 +65,7 @@ class Status < ApplicationRecord
has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :bookmarks, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
+ has_many :reblogged_by_accounts, through: :reblogs, class_name: 'Account', source: :account
has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :thread
has_many :mentions, dependent: :destroy, inverse_of: :status
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb
index 27b031fcc77..69b81f6deb0 100644
--- a/app/serializers/rest/notification_serializer.rb
+++ b/app/serializers/rest/notification_serializer.rb
@@ -11,6 +11,6 @@ class REST::NotificationSerializer < ActiveModel::Serializer
end
def status_type?
- [:favourite, :reblog, :status, :mention, :poll].include?(object.type)
+ [:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type)
end
end
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 46feec5aa33..08fae793583 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -34,6 +34,7 @@ class FanOutOnWriteService < BaseService
def fan_out_to_local_recipients!
deliver_to_self!
notify_mentioned_accounts!
+ notify_about_update! if update?
case @status.visibility.to_sym
when :public, :unlisted, :private
@@ -66,6 +67,14 @@ class FanOutOnWriteService < BaseService
end
end
+ def notify_about_update!
+ @status.reblogged_by_accounts.merge(Account.local).select(:id).reorder(nil).find_in_batches do |accounts|
+ LocalNotificationWorker.push_bulk(accounts) do |account|
+ [account.id, @status.id, 'Status', 'update']
+ end
+ end
+ end
+
def deliver_to_all_followers!
@account.followers_for_local_distribution.select(:id).reorder(nil).find_in_batches do |followers|
FeedInsertWorker.push_bulk(followers) do |follower|
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 0f3516d28f8..039e007f504 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -46,6 +46,10 @@ class NotifyService < BaseService
false
end
+ def blocked_update?
+ false
+ end
+
def following_sender?
return @following_sender if defined?(@following_sender)
@following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index e520bca0cc4..4db8fd15cbb 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -84,5 +84,5 @@
= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button'
- else
= link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }, class: 'button'
- - unless @instance.delivery_failure_tracker.available? && @instance.accounts_count > 0
+ - if !@instance.delivery_failure_tracker.available? || @instance.accounts_count.zero? || @instance.domain_block&.suspend?
= link_to t('admin.instances.purge'), admin_instance_path(@instance), data: { confirm: t('admin.instances.confirm_purge'), method: :delete }, class: 'button'
diff --git a/app/workers/local_notification_worker.rb b/app/workers/local_notification_worker.rb
index a22e2834de6..749a54b73b0 100644
--- a/app/workers/local_notification_worker.rb
+++ b/app/workers/local_notification_worker.rb
@@ -12,7 +12,14 @@ class LocalNotificationWorker
activity = activity_class_name.constantize.find(activity_id)
end
- return if Notification.where(account: receiver, activity: activity).any?
+ # For most notification types, only one notification should exist, and the older one is
+ # preferred. For updates, such as when a status is edited, the new notification
+ # should replace the previous ones.
+ if type == 'update'
+ Notification.where(account: receiver, activity: activity, type: 'update').in_batches.delete_all
+ elsif Notification.where(account: receiver, activity: activity, type: type).any?
+ return
+ end
NotifyService.new.call(receiver, type || activity_class_name.underscore, activity)
rescue ActiveRecord::RecordNotFound
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 0a9f66827c5..1809f123ed8 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1148,6 +1148,8 @@ en:
title: New boost
status:
subject: "%{name} just posted"
+ update:
+ subject: "%{name} edited a post"
notifications:
email_events: Events for e-mail notifications
email_events_hint: 'Select events that you want to receive notifications for:'
diff --git a/yarn.lock b/yarn.lock
index e0d3f9ba321..ae363b6f04a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4999,9 +4999,9 @@ flush-write-stream@^1.0.0:
readable-stream "^2.3.6"
follow-redirects@^1.0.0, follow-redirects@^1.14.7:
- version "1.14.7"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
- integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
+ version "1.14.8"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
+ integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
font-awesome@^4.7.0:
version "4.7.0"