Merge pull request #1688 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changespull/1689/head
commit
f61137b7ff
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -88,6 +88,14 @@ export default class StatusPrepend extends React.PureComponent {
|
|||
/>
|
||||
);
|
||||
}
|
||||
case 'update':
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='notification.update'
|
||||
defaultMessage='{name} edited a post'
|
||||
values={{ name: link }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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 : (
|
||||
|
|
|
@ -154,6 +154,17 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'status']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-update'>
|
||||
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
|
||||
|
||||
<div className='column-settings__pillbar'>
|
||||
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
|
||||
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'update']} onChange={this.onPushChange} label={pushStr} />}
|
||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'update']} onChange={onChange} label={showStr} />
|
||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -171,6 +171,28 @@ export default class Notification extends ImmutablePureComponent {
|
|||
unread={this.props.unread}
|
||||
/>
|
||||
);
|
||||
case 'update':
|
||||
return (
|
||||
<StatusContainer
|
||||
containerId={notification.get('id')}
|
||||
hidden={hidden}
|
||||
id={notification.get('status')}
|
||||
account={notification.get('account')}
|
||||
prepend='update'
|
||||
muted
|
||||
notification={notification}
|
||||
onMoveDown={onMoveDown}
|
||||
onMoveUp={onMoveUp}
|
||||
onMention={onMention}
|
||||
getScrollPosition={getScrollPosition}
|
||||
updateScrollBottom={updateScrollBottom}
|
||||
cachedMediaWidth={this.props.cachedMediaWidth}
|
||||
cacheMediaWidth={this.props.cacheMediaWidth}
|
||||
onUnmount={this.props.onUnmount}
|
||||
withDismiss
|
||||
unread={this.props.unread}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
}),
|
||||
|
||||
|
|
|
@ -1203,6 +1203,10 @@ a.sparkline {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 930px) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.account-card {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -153,6 +153,17 @@ export default class ColumnSettings extends React.PureComponent {
|
|||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'status']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-update'>
|
||||
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
|
||||
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'update']} onChange={this.onPushChange} label={pushStr} />}
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'update']} onChange={onChange} label={showStr} />
|
||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<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'>
|
||||
<div className='notification__favourite-icon-wrapper'>
|
||||
<Icon id='pencil' fixedWidth />
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.update' defaultMessage='{name} edited a post' values={{ name: link }} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<StatusContainer
|
||||
id={notification.get('status')}
|
||||
account={notification.get('account')}
|
||||
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 } = 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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
}),
|
||||
|
||||
|
|
|
@ -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'] || '',
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
|
||||
|
|
|
@ -1203,6 +1203,10 @@ a.sparkline {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 930px) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.account-card {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:'
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue