Add notifications for new sign-ups (#16953)
This commit is contained in:
		
							parent
							
								
									3e12abc1fe
								
							
						
					
					
						commit
						7b816eb5ae
					
				
					 11 changed files with 73 additions and 65 deletions
				
			
		| 
						 | 
				
			
			@ -17,17 +17,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
 | 
			
		|||
 | 
			
		||||
    data = {
 | 
			
		||||
      policy: 'all',
 | 
			
		||||
 | 
			
		||||
      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,
 | 
			
		||||
      },
 | 
			
		||||
      alerts: Notification::TYPES.index_with { alerts_enabled },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data.deep_merge!(data_params) if params[:data]
 | 
			
		||||
| 
						 | 
				
			
			@ -62,15 +52,6 @@ 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,
 | 
			
		||||
      :update,
 | 
			
		||||
    ])
 | 
			
		||||
    @data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ defineMessages({
 | 
			
		|||
});
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    dispatch(fetchRelationships(accountIds));
 | 
			
		||||
| 
						 | 
				
			
			@ -132,6 +132,7 @@ const excludeTypesFromFilter = filter => {
 | 
			
		|||
    'poll',
 | 
			
		||||
    'status',
 | 
			
		||||
    'update',
 | 
			
		||||
    'admin.sign_up',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  return allTypes.filterNot(item => item === filter).toJS();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
 | 
			
		|||
import ClearColumnButton from './clear_column_button';
 | 
			
		||||
import GrantPermissionButton from './grant_permission_button';
 | 
			
		||||
import SettingToggle from './setting_toggle';
 | 
			
		||||
import { isStaff } from 'mastodon/initial_state';
 | 
			
		||||
 | 
			
		||||
export default class ColumnSettings extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +156,7 @@ export default class ColumnSettings extends React.PureComponent {
 | 
			
		|||
        </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>
 | 
			
		||||
          <span id='notifications-update' 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} />
 | 
			
		||||
| 
						 | 
				
			
			@ -164,6 +165,19 @@ export default class ColumnSettings extends React.PureComponent {
 | 
			
		|||
            <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'update']} onChange={onChange} label={soundStr} />
 | 
			
		||||
          </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>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ const messages = defineMessages({
 | 
			
		|||
  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' },
 | 
			
		||||
  adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
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 () {
 | 
			
		||||
    const { notification } = this.props;
 | 
			
		||||
    const account          = notification.get('account');
 | 
			
		||||
| 
						 | 
				
			
			@ -367,6 +390,8 @@ class Notification extends ImmutablePureComponent {
 | 
			
		|||
      return this.renderUpdate(notification, link);
 | 
			
		||||
    case 'poll':
 | 
			
		||||
      return this.renderPoll(notification, account);
 | 
			
		||||
    case 'admin.sign_up':
 | 
			
		||||
      return this.renderAdminSignUp(notification, account, link);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,7 @@ const initialState = ImmutableMap({
 | 
			
		|||
      poll: false,
 | 
			
		||||
      status: false,
 | 
			
		||||
      update: false,
 | 
			
		||||
      'admin.sign_up': false,
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
    quickFilter: ImmutableMap({
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +58,7 @@ const initialState = ImmutableMap({
 | 
			
		|||
      poll: true,
 | 
			
		||||
      status: true,
 | 
			
		||||
      update: true,
 | 
			
		||||
      'admin.sign_up': true,
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
    sounds: ImmutableMap({
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +70,7 @@ const initialState = ImmutableMap({
 | 
			
		|||
      poll: true,
 | 
			
		||||
      status: true,
 | 
			
		||||
      update: true,
 | 
			
		||||
      'admin.sign_up': true,
 | 
			
		||||
    }),
 | 
			
		||||
  }),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ filenames.forEach(filename => {
 | 
			
		|||
    'notification.poll': full['notification.poll'] || '',
 | 
			
		||||
    'notification.status': full['notification.status'] || '',
 | 
			
		||||
    'notification.update': full['notification.update'] || '',
 | 
			
		||||
    'notification.admin.sign_up': full['notification.admin.sign_up'] || '',
 | 
			
		||||
 | 
			
		||||
    'status.show_more': full['status.show_more'] || '',
 | 
			
		||||
    'status.reblog': full['status.reblog'] || '',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ class Notification < ApplicationRecord
 | 
			
		|||
    favourite
 | 
			
		||||
    poll
 | 
			
		||||
    update
 | 
			
		||||
    admin.sign_up
 | 
			
		||||
  ).freeze
 | 
			
		||||
 | 
			
		||||
  TARGET_STATUS_INCLUDES_BY_TYPE = {
 | 
			
		||||
| 
						 | 
				
			
			@ -63,13 +64,10 @@ class Notification < ApplicationRecord
 | 
			
		|||
  scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
 | 
			
		||||
 | 
			
		||||
  scope :browserable, ->(exclude_types = [], account_id = nil) {
 | 
			
		||||
    types = TYPES - exclude_types.map(&:to_sym)
 | 
			
		||||
 | 
			
		||||
    if account_id.nil?
 | 
			
		||||
      where(type: types)
 | 
			
		||||
    else
 | 
			
		||||
      where(type: types, from_account_id: account_id)
 | 
			
		||||
    end
 | 
			
		||||
    scope = all
 | 
			
		||||
    scope = where(from_account_id: account_id) if account_id.present?
 | 
			
		||||
    scope = scope.where(type: TYPES - exclude_types.map(&:to_sym)) unless exclude_types.empty?
 | 
			
		||||
    scope
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  def type
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +140,8 @@ class Notification < ApplicationRecord
 | 
			
		|||
      self.from_account_id = activity&.account_id
 | 
			
		||||
    when 'Mention'
 | 
			
		||||
      self.from_account_id = activity&.status&.account_id
 | 
			
		||||
    when 'Account'
 | 
			
		||||
      self.from_account_id = activity&.id
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ class BootstrapTimelineService < BaseService
 | 
			
		|||
    @source_account = source_account
 | 
			
		||||
 | 
			
		||||
    autofollow_inviter!
 | 
			
		||||
    notify_staff!
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
| 
						 | 
				
			
			@ -14,4 +15,10 @@ class BootstrapTimelineService < BaseService
 | 
			
		|||
 | 
			
		||||
    FollowService.new.call(@source_account, @source_account.user.invite.user.account)
 | 
			
		||||
  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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,34 +22,6 @@ class NotifyService < BaseService
 | 
			
		|||
    FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
 | 
			
		||||
  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?
 | 
			
		||||
    return @following_sender if defined?(@following_sender)
 | 
			
		||||
    @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?
 | 
			
		||||
 | 
			
		||||
    blocked ||= domain_blocking?                                 # Skip for domain blocked accounts
 | 
			
		||||
    blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
 | 
			
		||||
    blocked ||= domain_blocking?
 | 
			
		||||
    blocked ||= @recipient.blocking?(@notification.from_account)
 | 
			
		||||
    blocked ||= @recipient.muting_notifications?(@notification.from_account)
 | 
			
		||||
    blocked ||= hellbanned?                                      # Hellban
 | 
			
		||||
    blocked ||= optional_non_follower?                           # Options
 | 
			
		||||
    blocked ||= optional_non_following?                          # Options
 | 
			
		||||
    blocked ||= optional_non_following_and_direct?               # Options
 | 
			
		||||
    blocked ||= hellbanned?
 | 
			
		||||
    blocked ||= optional_non_follower?
 | 
			
		||||
    blocked ||= optional_non_following?
 | 
			
		||||
    blocked ||= optional_non_following_and_direct?
 | 
			
		||||
    blocked ||= conversation_muted?
 | 
			
		||||
    blocked ||= send("blocked_#{@notification.type}?")           # Type-dependent filters
 | 
			
		||||
    blocked ||= blocked_mention? if @notification.type == :mention
 | 
			
		||||
    blocked
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,6 +61,7 @@ ignore_unused:
 | 
			
		|||
  - 'admin.action_logs.actions.*'
 | 
			
		||||
  - 'statuses.attached.*'
 | 
			
		||||
  - 'move_handler.carry_{mutes,blocks}_over_text'
 | 
			
		||||
  - 'notification_mailer.*'
 | 
			
		||||
 | 
			
		||||
ignore_inconsistent_interpolations:
 | 
			
		||||
  - '*.one'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1176,6 +1176,9 @@ en:
 | 
			
		|||
    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:'
 | 
			
		||||
  notification_mailer:
 | 
			
		||||
    admin:
 | 
			
		||||
      sign_up:
 | 
			
		||||
        subject: "%{name} signed up"
 | 
			
		||||
    digest:
 | 
			
		||||
      action: View all notifications
 | 
			
		||||
      body: Here is a brief summary of the messages you missed since your last visit on %{since}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue