Add notifications for new sign-ups (#16953)
parent
3e12abc1fe
commit
7b816eb5ae
|
@ -17,17 +17,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
policy: 'all',
|
policy: 'all',
|
||||||
|
alerts: Notification::TYPES.index_with { alerts_enabled },
|
||||||
alerts: {
|
|
||||||
follow: alerts_enabled,
|
|
||||||
follow_request: alerts_enabled,
|
|
||||||
favourite: alerts_enabled,
|
|
||||||
reblog: alerts_enabled,
|
|
||||||
mention: alerts_enabled,
|
|
||||||
poll: alerts_enabled,
|
|
||||||
status: alerts_enabled,
|
|
||||||
update: alerts_enabled,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data.deep_merge!(data_params) if params[:data]
|
data.deep_merge!(data_params) if params[:data]
|
||||||
|
@ -62,15 +52,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def data_params
|
def data_params
|
||||||
@data_params ||= params.require(:data).permit(:policy, alerts: [
|
@data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
|
||||||
:follow,
|
|
||||||
:follow_request,
|
|
||||||
:favourite,
|
|
||||||
:reblog,
|
|
||||||
:mention,
|
|
||||||
:poll,
|
|
||||||
:status,
|
|
||||||
:update,
|
|
||||||
])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,7 +45,7 @@ defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchRelatedRelationships = (dispatch, notifications) => {
|
const fetchRelatedRelationships = (dispatch, notifications) => {
|
||||||
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
|
const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
|
||||||
|
|
||||||
if (accountIds.length > 0) {
|
if (accountIds.length > 0) {
|
||||||
dispatch(fetchRelationships(accountIds));
|
dispatch(fetchRelationships(accountIds));
|
||||||
|
@ -132,6 +132,7 @@ const excludeTypesFromFilter = filter => {
|
||||||
'poll',
|
'poll',
|
||||||
'status',
|
'status',
|
||||||
'update',
|
'update',
|
||||||
|
'admin.sign_up',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return allTypes.filterNot(item => item === filter).toJS();
|
return allTypes.filterNot(item => item === filter).toJS();
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
||||||
import ClearColumnButton from './clear_column_button';
|
import ClearColumnButton from './clear_column_button';
|
||||||
import GrantPermissionButton from './grant_permission_button';
|
import GrantPermissionButton from './grant_permission_button';
|
||||||
import SettingToggle from './setting_toggle';
|
import SettingToggle from './setting_toggle';
|
||||||
|
import { isStaff } from 'mastodon/initial_state';
|
||||||
|
|
||||||
export default class ColumnSettings extends React.PureComponent {
|
export default class ColumnSettings extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@ export default class ColumnSettings extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div role='group' aria-labelledby='notifications-update'>
|
<div role='group' aria-labelledby='notifications-update'>
|
||||||
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
|
<span id='notifications-update' className='column-settings__section'><FormattedMessage id='notifications.column_settings.update' defaultMessage='Edits:' /></span>
|
||||||
|
|
||||||
<div className='column-settings__row'>
|
<div className='column-settings__row'>
|
||||||
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
|
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'update']} onChange={onChange} label={alertStr} />
|
||||||
|
@ -164,6 +165,19 @@ export default class ColumnSettings extends React.PureComponent {
|
||||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isStaff && (
|
||||||
|
<div role='group' aria-labelledby='notifications-admin-sign-up'>
|
||||||
|
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span>
|
||||||
|
|
||||||
|
<div className='column-settings__row'>
|
||||||
|
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'admin.sign_up']} onChange={onChange} label={alertStr} />
|
||||||
|
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'admin.sign_up']} onChange={this.onPushChange} label={pushStr} />}
|
||||||
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'admin.sign_up']} onChange={onChange} label={showStr} />
|
||||||
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'admin.sign_up']} onChange={onChange} label={soundStr} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ const messages = defineMessages({
|
||||||
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
|
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
|
||||||
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
|
status: { id: 'notification.status', defaultMessage: '{name} just posted' },
|
||||||
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
|
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
|
||||||
|
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const notificationForScreenReader = (intl, message, timestamp) => {
|
const notificationForScreenReader = (intl, message, timestamp) => {
|
||||||
|
@ -344,6 +345,28 @@ class Notification extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderAdminSignUp (notification, account, link) {
|
||||||
|
const { intl, unread } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HotKeys handlers={this.getHandlers()}>
|
||||||
|
<div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}>
|
||||||
|
<div className='notification__message'>
|
||||||
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
|
<Icon id='user-plus' fixedWidth />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span title={notification.get('created_at')}>
|
||||||
|
<FormattedMessage id='notification.admin.sign_up' defaultMessage='{name} signed up' values={{ name: link }} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AccountContainer id={account.get('id')} hidden={this.props.hidden} />
|
||||||
|
</div>
|
||||||
|
</HotKeys>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { notification } = this.props;
|
const { notification } = this.props;
|
||||||
const account = notification.get('account');
|
const account = notification.get('account');
|
||||||
|
@ -367,6 +390,8 @@ class Notification extends ImmutablePureComponent {
|
||||||
return this.renderUpdate(notification, link);
|
return this.renderUpdate(notification, link);
|
||||||
case 'poll':
|
case 'poll':
|
||||||
return this.renderPoll(notification, account);
|
return this.renderPoll(notification, account);
|
||||||
|
case 'admin.sign_up':
|
||||||
|
return this.renderAdminSignUp(notification, account, link);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -37,6 +37,7 @@ const initialState = ImmutableMap({
|
||||||
poll: false,
|
poll: false,
|
||||||
status: false,
|
status: false,
|
||||||
update: false,
|
update: false,
|
||||||
|
'admin.sign_up': false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
quickFilter: ImmutableMap({
|
quickFilter: ImmutableMap({
|
||||||
|
@ -57,6 +58,7 @@ const initialState = ImmutableMap({
|
||||||
poll: true,
|
poll: true,
|
||||||
status: true,
|
status: true,
|
||||||
update: true,
|
update: true,
|
||||||
|
'admin.sign_up': true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
sounds: ImmutableMap({
|
sounds: ImmutableMap({
|
||||||
|
@ -68,6 +70,7 @@ const initialState = ImmutableMap({
|
||||||
poll: true,
|
poll: true,
|
||||||
status: true,
|
status: true,
|
||||||
update: true,
|
update: true,
|
||||||
|
'admin.sign_up': true,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ filenames.forEach(filename => {
|
||||||
'notification.poll': full['notification.poll'] || '',
|
'notification.poll': full['notification.poll'] || '',
|
||||||
'notification.status': full['notification.status'] || '',
|
'notification.status': full['notification.status'] || '',
|
||||||
'notification.update': full['notification.update'] || '',
|
'notification.update': full['notification.update'] || '',
|
||||||
|
'notification.admin.sign_up': full['notification.admin.sign_up'] || '',
|
||||||
|
|
||||||
'status.show_more': full['status.show_more'] || '',
|
'status.show_more': full['status.show_more'] || '',
|
||||||
'status.reblog': full['status.reblog'] || '',
|
'status.reblog': full['status.reblog'] || '',
|
||||||
|
|
|
@ -36,6 +36,7 @@ class Notification < ApplicationRecord
|
||||||
favourite
|
favourite
|
||||||
poll
|
poll
|
||||||
update
|
update
|
||||||
|
admin.sign_up
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
TARGET_STATUS_INCLUDES_BY_TYPE = {
|
TARGET_STATUS_INCLUDES_BY_TYPE = {
|
||||||
|
@ -63,13 +64,10 @@ class Notification < ApplicationRecord
|
||||||
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
|
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
|
||||||
|
|
||||||
scope :browserable, ->(exclude_types = [], account_id = nil) {
|
scope :browserable, ->(exclude_types = [], account_id = nil) {
|
||||||
types = TYPES - exclude_types.map(&:to_sym)
|
scope = all
|
||||||
|
scope = where(from_account_id: account_id) if account_id.present?
|
||||||
if account_id.nil?
|
scope = scope.where(type: TYPES - exclude_types.map(&:to_sym)) unless exclude_types.empty?
|
||||||
where(type: types)
|
scope
|
||||||
else
|
|
||||||
where(type: types, from_account_id: account_id)
|
|
||||||
end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def type
|
def type
|
||||||
|
@ -142,6 +140,8 @@ class Notification < ApplicationRecord
|
||||||
self.from_account_id = activity&.account_id
|
self.from_account_id = activity&.account_id
|
||||||
when 'Mention'
|
when 'Mention'
|
||||||
self.from_account_id = activity&.status&.account_id
|
self.from_account_id = activity&.status&.account_id
|
||||||
|
when 'Account'
|
||||||
|
self.from_account_id = activity&.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ class BootstrapTimelineService < BaseService
|
||||||
@source_account = source_account
|
@source_account = source_account
|
||||||
|
|
||||||
autofollow_inviter!
|
autofollow_inviter!
|
||||||
|
notify_staff!
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -14,4 +15,10 @@ class BootstrapTimelineService < BaseService
|
||||||
|
|
||||||
FollowService.new.call(@source_account, @source_account.user.invite.user.account)
|
FollowService.new.call(@source_account, @source_account.user.invite.user.account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notify_staff!
|
||||||
|
User.staff.includes(:account).find_each do |user|
|
||||||
|
NotifyService.new.call(user.account, :'admin.new_user', @source_account)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,34 +22,6 @@ class NotifyService < BaseService
|
||||||
FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
|
FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocked_status?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def blocked_favourite?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def blocked_follow?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def blocked_reblog?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def blocked_follow_request?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def blocked_poll?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def blocked_update?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def following_sender?
|
def following_sender?
|
||||||
return @following_sender if defined?(@following_sender)
|
return @following_sender if defined?(@following_sender)
|
||||||
@following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
|
@following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
|
||||||
|
@ -149,15 +121,15 @@ class NotifyService < BaseService
|
||||||
|
|
||||||
return blocked if message? && from_staff?
|
return blocked if message? && from_staff?
|
||||||
|
|
||||||
blocked ||= domain_blocking? # Skip for domain blocked accounts
|
blocked ||= domain_blocking?
|
||||||
blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
|
blocked ||= @recipient.blocking?(@notification.from_account)
|
||||||
blocked ||= @recipient.muting_notifications?(@notification.from_account)
|
blocked ||= @recipient.muting_notifications?(@notification.from_account)
|
||||||
blocked ||= hellbanned? # Hellban
|
blocked ||= hellbanned?
|
||||||
blocked ||= optional_non_follower? # Options
|
blocked ||= optional_non_follower?
|
||||||
blocked ||= optional_non_following? # Options
|
blocked ||= optional_non_following?
|
||||||
blocked ||= optional_non_following_and_direct? # Options
|
blocked ||= optional_non_following_and_direct?
|
||||||
blocked ||= conversation_muted?
|
blocked ||= conversation_muted?
|
||||||
blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters
|
blocked ||= blocked_mention? if @notification.type == :mention
|
||||||
blocked
|
blocked
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ ignore_unused:
|
||||||
- 'admin.action_logs.actions.*'
|
- 'admin.action_logs.actions.*'
|
||||||
- 'statuses.attached.*'
|
- 'statuses.attached.*'
|
||||||
- 'move_handler.carry_{mutes,blocks}_over_text'
|
- 'move_handler.carry_{mutes,blocks}_over_text'
|
||||||
|
- 'notification_mailer.*'
|
||||||
|
|
||||||
ignore_inconsistent_interpolations:
|
ignore_inconsistent_interpolations:
|
||||||
- '*.one'
|
- '*.one'
|
||||||
|
|
|
@ -1176,6 +1176,9 @@ en:
|
||||||
carry_mutes_over_text: This user moved from %{acct}, which you had muted.
|
carry_mutes_over_text: This user moved from %{acct}, which you had muted.
|
||||||
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
|
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
|
||||||
notification_mailer:
|
notification_mailer:
|
||||||
|
admin:
|
||||||
|
sign_up:
|
||||||
|
subject: "%{name} signed up"
|
||||||
digest:
|
digest:
|
||||||
action: View all notifications
|
action: View all notifications
|
||||||
body: Here is a brief summary of the messages you missed since your last visit on %{since}
|
body: Here is a brief summary of the messages you missed since your last visit on %{since}
|
||||||
|
|
Reference in New Issue