Merge commit '41a505513fb36f7c28c8d8a4270d5ee192169462' into glitch-soc/merge-upstream
Conflicts: - `app/serializers/initial_state_serializer.rb`: Upstream renamed an initial state parameter, where we had extra ones. Renamed as upstream did. - `app/workers/feed_insert_worker.rb`: Upstream wrapped database query in a block, we had extra database queries because of the DM timeline. Moved everything in the block.
This commit is contained in:
		@@ -6,11 +6,14 @@ class Api::V1::Timelines::HomeController < Api::BaseController
 | 
			
		||||
  after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    @statuses = load_statuses
 | 
			
		||||
    ApplicationRecord.connected_to(role: :read, prevent_writes: true) do
 | 
			
		||||
      @statuses = load_statuses
 | 
			
		||||
      @relationships = StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    render json: @statuses,
 | 
			
		||||
           each_serializer: REST::StatusSerializer,
 | 
			
		||||
           relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
 | 
			
		||||
           relationships: @relationships,
 | 
			
		||||
           status: account_home_feed.regenerating? ? 206 : 200
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,52 +12,48 @@ export const ALERT_DISMISS = 'ALERT_DISMISS';
 | 
			
		||||
export const ALERT_CLEAR   = 'ALERT_CLEAR';
 | 
			
		||||
export const ALERT_NOOP    = 'ALERT_NOOP';
 | 
			
		||||
 | 
			
		||||
export function dismissAlert(alert) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: ALERT_DISMISS,
 | 
			
		||||
    alert,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
export const dismissAlert = alert => ({
 | 
			
		||||
  type: ALERT_DISMISS,
 | 
			
		||||
  alert,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function clearAlert() {
 | 
			
		||||
  return {
 | 
			
		||||
    type: ALERT_CLEAR,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
export const clearAlert = () => ({
 | 
			
		||||
  type: ALERT_CLEAR,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, message_values = undefined) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: ALERT_SHOW,
 | 
			
		||||
    title,
 | 
			
		||||
    message,
 | 
			
		||||
    message_values,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
export const showAlert = alert => ({
 | 
			
		||||
  type: ALERT_SHOW,
 | 
			
		||||
  alert,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function showAlertForError(error, skipNotFound = false) {
 | 
			
		||||
export const showAlertForError = (error, skipNotFound = false) => {
 | 
			
		||||
  if (error.response) {
 | 
			
		||||
    const { data, status, statusText, headers } = error.response;
 | 
			
		||||
 | 
			
		||||
    // Skip these errors as they are reflected in the UI
 | 
			
		||||
    if (skipNotFound && (status === 404 || status === 410)) {
 | 
			
		||||
      // Skip these errors as they are reflected in the UI
 | 
			
		||||
      return { type: ALERT_NOOP };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Rate limit errors
 | 
			
		||||
    if (status === 429 && headers['x-ratelimit-reset']) {
 | 
			
		||||
      const reset_date = new Date(headers['x-ratelimit-reset']);
 | 
			
		||||
      return showAlert(messages.rateLimitedTitle, messages.rateLimitedMessage, { 'retry_time': reset_date });
 | 
			
		||||
      return showAlert({
 | 
			
		||||
        title: messages.rateLimitedTitle,
 | 
			
		||||
        message: messages.rateLimitedMessage,
 | 
			
		||||
        values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let message = statusText;
 | 
			
		||||
    let title   = `${status}`;
 | 
			
		||||
 | 
			
		||||
    if (data.error) {
 | 
			
		||||
      message = data.error;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return showAlert(title, message);
 | 
			
		||||
  } else {
 | 
			
		||||
    console.error(error);
 | 
			
		||||
    return showAlert();
 | 
			
		||||
    return showAlert({
 | 
			
		||||
      title: `${status}`,
 | 
			
		||||
      message: data.error || statusText,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  console.error(error);
 | 
			
		||||
 | 
			
		||||
  return showAlert({
 | 
			
		||||
    title: messages.unexpectedTitle,
 | 
			
		||||
    message: messages.unexpectedMessage,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,8 @@ export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
 | 
			
		||||
  uploadErrorPoll:  { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
 | 
			
		||||
  open: { id: 'compose.published.open', defaultMessage: 'Open' },
 | 
			
		||||
  published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const ensureComposeIsVisible = (getState, routerHistory) => {
 | 
			
		||||
@@ -242,6 +244,13 @@ export function submitCompose(routerHistory) {
 | 
			
		||||
        }
 | 
			
		||||
        insertIfOnline(`account:${response.data.account.id}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      dispatch(showAlert({
 | 
			
		||||
        message: messages.published,
 | 
			
		||||
        action: messages.open,
 | 
			
		||||
        dismissAfter: 10000,
 | 
			
		||||
        onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),
 | 
			
		||||
      }));
 | 
			
		||||
    }).catch(function (error) {
 | 
			
		||||
      dispatch(submitComposeFail(error));
 | 
			
		||||
    });
 | 
			
		||||
@@ -271,18 +280,19 @@ export function submitComposeFail(error) {
 | 
			
		||||
export function uploadCompose(files) {
 | 
			
		||||
  return function (dispatch, getState) {
 | 
			
		||||
    const uploadLimit = 4;
 | 
			
		||||
    const media  = getState().getIn(['compose', 'media_attachments']);
 | 
			
		||||
    const pending  = getState().getIn(['compose', 'pending_media_attachments']);
 | 
			
		||||
    const media = getState().getIn(['compose', 'media_attachments']);
 | 
			
		||||
    const pending = getState().getIn(['compose', 'pending_media_attachments']);
 | 
			
		||||
    const progress = new Array(files.length).fill(0);
 | 
			
		||||
 | 
			
		||||
    let total = Array.from(files).reduce((a, v) => a + v.size, 0);
 | 
			
		||||
 | 
			
		||||
    if (files.length + media.size + pending > uploadLimit) {
 | 
			
		||||
      dispatch(showAlert(undefined, messages.uploadErrorLimit));
 | 
			
		||||
      dispatch(showAlert({ message: messages.uploadErrorLimit }));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (getState().getIn(['compose', 'poll'])) {
 | 
			
		||||
      dispatch(showAlert(undefined, messages.uploadErrorPoll));
 | 
			
		||||
      dispatch(showAlert({ message: messages.uploadErrorPoll }));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -237,7 +237,6 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		||||
    const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
 | 
			
		||||
    const { signedIn, permissions } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    const anonymousAccess    = !signedIn;
 | 
			
		||||
    const publicStatus       = ['public', 'unlisted'].includes(status.get('visibility'));
 | 
			
		||||
    const pinnableStatus     = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
 | 
			
		||||
    const mutingConversation = status.get('muted');
 | 
			
		||||
@@ -263,71 +262,73 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    menu.push(null);
 | 
			
		||||
 | 
			
		||||
    menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
 | 
			
		||||
 | 
			
		||||
    if (writtenByMe && pinnableStatus) {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    menu.push(null);
 | 
			
		||||
 | 
			
		||||
    if (writtenByMe || withDismiss) {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
 | 
			
		||||
      menu.push(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (writtenByMe) {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
 | 
			
		||||
    } else {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      menu.push(null);
 | 
			
		||||
 | 
			
		||||
      if (relationship && relationship.get('muting')) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
 | 
			
		||||
 | 
			
		||||
      if (writtenByMe && pinnableStatus) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      menu.push(null);
 | 
			
		||||
 | 
			
		||||
      if (writtenByMe || withDismiss) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (writtenByMe) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
 | 
			
		||||
      } else {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (relationship && relationship.get('blocking')) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
 | 
			
		||||
      } else {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!this.props.onFilter) {
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.filter), action: this.handleFilterClick, dangerous: true });
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });
 | 
			
		||||
 | 
			
		||||
      if (account.get('acct') !== account.get('username')) {
 | 
			
		||||
        const domain = account.get('acct').split('@')[1];
 | 
			
		||||
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
 | 
			
		||||
        if (relationship && relationship.get('domain_blocking')) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
 | 
			
		||||
        if (relationship && relationship.get('muting')) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
 | 
			
		||||
        } else {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
 | 
			
		||||
        if (relationship && relationship.get('blocking')) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
 | 
			
		||||
        } else {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
 | 
			
		||||
        }
 | 
			
		||||
        if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
 | 
			
		||||
 | 
			
		||||
        if (!this.props.onFilter) {
 | 
			
		||||
          menu.push(null);
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.filter), action: this.handleFilterClick, dangerous: true });
 | 
			
		||||
          menu.push(null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });
 | 
			
		||||
 | 
			
		||||
        if (account.get('acct') !== account.get('username')) {
 | 
			
		||||
          const domain = account.get('acct').split('@')[1];
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
 | 
			
		||||
 | 
			
		||||
          menu.push(null);
 | 
			
		||||
 | 
			
		||||
          if (relationship && relationship.get('domain_blocking')) {
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
 | 
			
		||||
          } else {
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
 | 
			
		||||
          menu.push(null);
 | 
			
		||||
          if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
 | 
			
		||||
          }
 | 
			
		||||
          if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
 | 
			
		||||
            const domain = account.get('acct').split('@')[1];
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -371,7 +372,6 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		||||
        <div className='status__action-bar__dropdown'>
 | 
			
		||||
          <DropdownMenuContainer
 | 
			
		||||
            scrollKey={scrollKey}
 | 
			
		||||
            disabled={anonymousAccess}
 | 
			
		||||
            status={status}
 | 
			
		||||
            items={menu}
 | 
			
		||||
            icon='ellipsis-h'
 | 
			
		||||
 
 | 
			
		||||
@@ -290,7 +290,6 @@ class Header extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
    if (isRemote) {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
 | 
			
		||||
      menu.push(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ('share' in navigator) {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import { connect } from 'react-redux';
 | 
			
		||||
import Column from 'mastodon/components/column';
 | 
			
		||||
import ColumnHeader from 'mastodon/components/column_header';
 | 
			
		||||
import Search from 'mastodon/features/compose/containers/search_container';
 | 
			
		||||
import { showTrends } from 'mastodon/initial_state';
 | 
			
		||||
import { trendsEnabled } from 'mastodon/initial_state';
 | 
			
		||||
 | 
			
		||||
import Links from './links';
 | 
			
		||||
import SearchResults from './results';
 | 
			
		||||
@@ -26,7 +26,7 @@ const messages = defineMessages({
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = state => ({
 | 
			
		||||
  layout: state.getIn(['meta', 'layout']),
 | 
			
		||||
  isSearching: state.getIn(['search', 'submitted']) || !showTrends,
 | 
			
		||||
  isSearching: state.getIn(['search', 'submitted']) || !trendsEnabled,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class Explore extends PureComponent {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
			
		||||
          if (permission === 'granted') {
 | 
			
		||||
            dispatch(changePushNotifications(path.slice(1), checked));
 | 
			
		||||
          } else {
 | 
			
		||||
            dispatch(showAlert(undefined, messages.permissionDenied));
 | 
			
		||||
            dispatch(showAlert({ message: messages.permissionDenied }));
 | 
			
		||||
          }
 | 
			
		||||
        }));
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -47,7 +47,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
			
		||||
          if (permission === 'granted') {
 | 
			
		||||
            dispatch(changeSetting(['notifications', ...path], checked));
 | 
			
		||||
          } else {
 | 
			
		||||
            dispatch(showAlert(undefined, messages.permissionDenied));
 | 
			
		||||
            dispatch(showAlert({ message: messages.permissionDenied }));
 | 
			
		||||
          }
 | 
			
		||||
        }));
 | 
			
		||||
      } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -195,71 +195,74 @@ class ActionBar extends PureComponent {
 | 
			
		||||
 | 
			
		||||
    let menu = [];
 | 
			
		||||
 | 
			
		||||
    if (publicStatus) {
 | 
			
		||||
      if (isRemote) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
 | 
			
		||||
 | 
			
		||||
      if ('share' in navigator) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.share), action: this.handleShare });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
 | 
			
		||||
      menu.push(null);
 | 
			
		||||
    if (publicStatus && isRemote) {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (writtenByMe) {
 | 
			
		||||
      if (pinnableStatus) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
      }
 | 
			
		||||
    menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
 | 
			
		||||
 | 
			
		||||
      menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
 | 
			
		||||
      menu.push(null);
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
 | 
			
		||||
    } else {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
 | 
			
		||||
    if (publicStatus && 'share' in navigator) {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.share), action: this.handleShare });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (publicStatus) {
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      menu.push(null);
 | 
			
		||||
 | 
			
		||||
      if (relationship && relationship.get('muting')) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
 | 
			
		||||
      if (writtenByMe) {
 | 
			
		||||
        if (pinnableStatus) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
 | 
			
		||||
          menu.push(null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
 | 
			
		||||
      } else {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (relationship && relationship.get('blocking')) {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
 | 
			
		||||
      } else {
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport, dangerous: true });
 | 
			
		||||
 | 
			
		||||
      if (account.get('acct') !== account.get('username')) {
 | 
			
		||||
        const domain = account.get('acct').split('@')[1];
 | 
			
		||||
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
 | 
			
		||||
        if (relationship && relationship.get('domain_blocking')) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
 | 
			
		||||
        if (relationship && relationship.get('muting')) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
 | 
			
		||||
        } else {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
 | 
			
		||||
        if (relationship && relationship.get('blocking')) {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
 | 
			
		||||
        } else {
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
 | 
			
		||||
        }
 | 
			
		||||
        if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
 | 
			
		||||
 | 
			
		||||
        menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport, dangerous: true });
 | 
			
		||||
 | 
			
		||||
        if (account.get('acct') !== account.get('username')) {
 | 
			
		||||
          const domain = account.get('acct').split('@')[1];
 | 
			
		||||
          menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
 | 
			
		||||
 | 
			
		||||
          menu.push(null);
 | 
			
		||||
 | 
			
		||||
          if (relationship && relationship.get('domain_blocking')) {
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
 | 
			
		||||
          } else {
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
 | 
			
		||||
          menu.push(null);
 | 
			
		||||
          if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
 | 
			
		||||
          }
 | 
			
		||||
          if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
 | 
			
		||||
            const domain = account.get('acct').split('@')[1];
 | 
			
		||||
            menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -292,7 +295,7 @@ class ActionBar extends PureComponent {
 | 
			
		||||
        <div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
 | 
			
		||||
 | 
			
		||||
        <div className='detailed-status__action-bar-dropdown'>
 | 
			
		||||
          <DropdownMenuContainer size={18} icon='ellipsis-h' disabled={!signedIn} status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
 | 
			
		||||
          <DropdownMenuContainer size={18} icon='ellipsis-h' status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import { Link } from 'react-router-dom';
 | 
			
		||||
 | 
			
		||||
import { WordmarkLogo } from 'mastodon/components/logo';
 | 
			
		||||
import NavigationPortal from 'mastodon/components/navigation_portal';
 | 
			
		||||
import { timelinePreview, showTrends } from 'mastodon/initial_state';
 | 
			
		||||
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
 | 
			
		||||
 | 
			
		||||
import ColumnLink from './column_link';
 | 
			
		||||
import DisabledAccountBanner from './disabled_account_banner';
 | 
			
		||||
@@ -65,7 +65,7 @@ class NavigationPanel extends Component {
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {showTrends ? (
 | 
			
		||||
        {trendsEnabled ? (
 | 
			
		||||
          <ColumnLink transparent to='/explore' icon='hashtag' text={intl.formatMessage(messages.explore)} />
 | 
			
		||||
        ) : (
 | 
			
		||||
          <ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />
 | 
			
		||||
 
 | 
			
		||||
@@ -7,26 +7,27 @@ import { NotificationStack } from 'react-notification';
 | 
			
		||||
import { dismissAlert } from '../../../actions/alerts';
 | 
			
		||||
import { getAlerts } from '../../../selectors';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { intl }) => {
 | 
			
		||||
  const notifications = getAlerts(state);
 | 
			
		||||
const formatIfNeeded = (intl, message, values) => {
 | 
			
		||||
  if (typeof message === 'object') {
 | 
			
		||||
    return intl.formatMessage(message, values);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  notifications.forEach(notification => ['title', 'message'].forEach(key => {
 | 
			
		||||
    const value = notification[key];
 | 
			
		||||
 | 
			
		||||
    if (typeof value === 'object') {
 | 
			
		||||
      notification[key] = intl.formatMessage(value, notification[`${key}_values`]);
 | 
			
		||||
    }
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  return { notifications };
 | 
			
		||||
  return message;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = (dispatch) => {
 | 
			
		||||
  return {
 | 
			
		||||
    onDismiss: alert => {
 | 
			
		||||
      dispatch(dismissAlert(alert));
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
const mapStateToProps = (state, { intl }) => ({
 | 
			
		||||
  notifications: getAlerts(state).map(alert => ({
 | 
			
		||||
    ...alert,
 | 
			
		||||
    action: formatIfNeeded(intl, alert.action, alert.values),
 | 
			
		||||
    title: formatIfNeeded(intl, alert.title, alert.values),
 | 
			
		||||
    message: formatIfNeeded(intl, alert.message, alert.values),
 | 
			
		||||
  })),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = (dispatch) => ({
 | 
			
		||||
  onDismiss (alert) {
 | 
			
		||||
    dispatch(dismissAlert(alert));
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationStack));
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ import { clearHeight } from '../../actions/height_cache';
 | 
			
		||||
import { expandNotifications } from '../../actions/notifications';
 | 
			
		||||
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
 | 
			
		||||
import { expandHomeTimeline } from '../../actions/timelines';
 | 
			
		||||
import initialState, { me, owner, singleUserMode, showTrends, trendsAsLanding } from '../../initial_state';
 | 
			
		||||
import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding } from '../../initial_state';
 | 
			
		||||
 | 
			
		||||
import BundleColumnError from './components/bundle_column_error';
 | 
			
		||||
import Header from './components/header';
 | 
			
		||||
@@ -170,7 +170,7 @@ class SwitchingColumnsArea extends PureComponent {
 | 
			
		||||
      }
 | 
			
		||||
    } else if (singleUserMode && owner && initialState?.accounts[owner]) {
 | 
			
		||||
      redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
 | 
			
		||||
    } else if (showTrends && trendsAsLanding) {
 | 
			
		||||
    } else if (trendsEnabled && trendsAsLanding) {
 | 
			
		||||
      redirect = <Redirect from='/' to='/explore' exact />;
 | 
			
		||||
    } else {
 | 
			
		||||
      redirect = <Redirect from='/' to='/about' exact />;
 | 
			
		||||
 
 | 
			
		||||
@@ -69,12 +69,13 @@
 | 
			
		||||
 * @property {boolean} reduce_motion
 | 
			
		||||
 * @property {string} repository
 | 
			
		||||
 * @property {boolean} search_enabled
 | 
			
		||||
 * @property {boolean} trends_enabled
 | 
			
		||||
 * @property {boolean} single_user_mode
 | 
			
		||||
 * @property {string} source_url
 | 
			
		||||
 * @property {string} streaming_api_base_url
 | 
			
		||||
 * @property {boolean} timeline_preview
 | 
			
		||||
 * @property {string} title
 | 
			
		||||
 * @property {boolean} trends
 | 
			
		||||
 * @property {boolean} show_trends
 | 
			
		||||
 * @property {boolean} trends_as_landing_page
 | 
			
		||||
 * @property {boolean} unfollow_modal
 | 
			
		||||
 * @property {boolean} use_blurhash
 | 
			
		||||
@@ -122,7 +123,8 @@ export const reduceMotion = getMeta('reduce_motion');
 | 
			
		||||
export const registrationsOpen = getMeta('registrations_open');
 | 
			
		||||
export const repository = getMeta('repository');
 | 
			
		||||
export const searchEnabled = getMeta('search_enabled');
 | 
			
		||||
export const showTrends = getMeta('trends');
 | 
			
		||||
export const trendsEnabled = getMeta('trends_enabled');
 | 
			
		||||
export const showTrends = getMeta('show_trends');
 | 
			
		||||
export const singleUserMode = getMeta('single_user_mode');
 | 
			
		||||
export const source_url = getMeta('source_url');
 | 
			
		||||
export const timelinePreview = getMeta('timeline_preview');
 | 
			
		||||
 
 | 
			
		||||
@@ -135,6 +135,8 @@
 | 
			
		||||
  "community.column_settings.remote_only": "Remote only",
 | 
			
		||||
  "compose.language.change": "Change language",
 | 
			
		||||
  "compose.language.search": "Search languages...",
 | 
			
		||||
  "compose.published.body": "Post published.",
 | 
			
		||||
  "compose.published.open": "Open",
 | 
			
		||||
  "compose_form.direct_message_warning_learn_more": "Learn more",
 | 
			
		||||
  "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any sensitive information over Mastodon.",
 | 
			
		||||
  "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is not public. Only public posts can be searched by hashtag.",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 | 
			
		||||
import { List as ImmutableList } from 'immutable';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  ALERT_SHOW,
 | 
			
		||||
@@ -8,17 +8,20 @@ import {
 | 
			
		||||
 | 
			
		||||
const initialState = ImmutableList([]);
 | 
			
		||||
 | 
			
		||||
let id = 0;
 | 
			
		||||
 | 
			
		||||
const addAlert = (state, alert) =>
 | 
			
		||||
  state.push({
 | 
			
		||||
    key: id++,
 | 
			
		||||
    ...alert,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
export default function alerts(state = initialState, action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case ALERT_SHOW:
 | 
			
		||||
    return state.push(ImmutableMap({
 | 
			
		||||
      key: state.size > 0 ? state.last().get('key') + 1 : 0,
 | 
			
		||||
      title: action.title,
 | 
			
		||||
      message: action.message,
 | 
			
		||||
      message_values: action.message_values,
 | 
			
		||||
    }));
 | 
			
		||||
    return addAlert(state, action.alert);
 | 
			
		||||
  case ALERT_DISMISS:
 | 
			
		||||
    return state.filterNot(item => item.get('key') === action.alert.key);
 | 
			
		||||
    return state.filterNot(item => item.key === action.alert.key);
 | 
			
		||||
  case ALERT_CLEAR:
 | 
			
		||||
    return state.clear();
 | 
			
		||||
  default:
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,6 @@ import lists from './lists';
 | 
			
		||||
import markers from './markers';
 | 
			
		||||
import media_attachments from './media_attachments';
 | 
			
		||||
import meta from './meta';
 | 
			
		||||
import { missedUpdatesReducer } from './missed_updates';
 | 
			
		||||
import { modalReducer } from './modal';
 | 
			
		||||
import mutes from './mutes';
 | 
			
		||||
import notifications from './notifications';
 | 
			
		||||
@@ -82,7 +81,6 @@ const reducers = {
 | 
			
		||||
  suggestions,
 | 
			
		||||
  polls,
 | 
			
		||||
  trends,
 | 
			
		||||
  missed_updates: missedUpdatesReducer,
 | 
			
		||||
  markers,
 | 
			
		||||
  picture_in_picture,
 | 
			
		||||
  history,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
import { Record } from 'immutable';
 | 
			
		||||
 | 
			
		||||
import type { Action } from 'redux';
 | 
			
		||||
 | 
			
		||||
import { focusApp, unfocusApp } from '../actions/app';
 | 
			
		||||
import { NOTIFICATIONS_UPDATE } from '../actions/notifications';
 | 
			
		||||
 | 
			
		||||
interface MissedUpdatesState {
 | 
			
		||||
  focused: boolean;
 | 
			
		||||
  unread: number;
 | 
			
		||||
}
 | 
			
		||||
const initialState = Record<MissedUpdatesState>({
 | 
			
		||||
  focused: true,
 | 
			
		||||
  unread: 0,
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
export function missedUpdatesReducer(
 | 
			
		||||
  state = initialState,
 | 
			
		||||
  action: Action<string>
 | 
			
		||||
) {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case focusApp.type:
 | 
			
		||||
      return state.set('focused', true).set('unread', 0);
 | 
			
		||||
    case unfocusApp.type:
 | 
			
		||||
      return state.set('focused', false);
 | 
			
		||||
    case NOTIFICATIONS_UPDATE:
 | 
			
		||||
      return state.get('focused')
 | 
			
		||||
        ? state
 | 
			
		||||
        : state.update('unread', (x) => x + 1);
 | 
			
		||||
    default:
 | 
			
		||||
      return state;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -84,26 +84,16 @@ export const makeGetPictureInPicture = () => {
 | 
			
		||||
  }));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getAlertsBase = state => state.get('alerts');
 | 
			
		||||
const ALERT_DEFAULTS = {
 | 
			
		||||
  dismissAfter: 5000,
 | 
			
		||||
  style: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getAlerts = createSelector([getAlertsBase], (base) => {
 | 
			
		||||
  let arr = [];
 | 
			
		||||
 | 
			
		||||
  base.forEach(item => {
 | 
			
		||||
    arr.push({
 | 
			
		||||
      message: item.get('message'),
 | 
			
		||||
      message_values: item.get('message_values'),
 | 
			
		||||
      title: item.get('title'),
 | 
			
		||||
      key: item.get('key'),
 | 
			
		||||
      dismissAfter: 5000,
 | 
			
		||||
      barStyle: {
 | 
			
		||||
        zIndex: 200,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return arr;
 | 
			
		||||
});
 | 
			
		||||
export const getAlerts = createSelector(state => state.get('alerts'), alerts =>
 | 
			
		||||
  alerts.map(item => ({
 | 
			
		||||
    ...ALERT_DEFAULTS,
 | 
			
		||||
    ...item,
 | 
			
		||||
  })).toArray());
 | 
			
		||||
 | 
			
		||||
export const makeGetNotification = () => createSelector([
 | 
			
		||||
  (_, base)             => base,
 | 
			
		||||
 
 | 
			
		||||
@@ -9077,3 +9077,62 @@ noscript {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification-list {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  bottom: 2rem;
 | 
			
		||||
  inset-inline-start: 0;
 | 
			
		||||
  z-index: 999;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  gap: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification-bar {
 | 
			
		||||
  flex: 0 0 auto;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  inset-inline-start: -100%;
 | 
			
		||||
  width: auto;
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  color: $primary-text-color;
 | 
			
		||||
  background: rgba($black, 0.85);
 | 
			
		||||
  backdrop-filter: blur(8px);
 | 
			
		||||
  border: 1px solid rgba(lighten($ui-base-color, 4%), 0.85);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  box-shadow: 0 10px 15px -3px rgba($base-shadow-color, 0.25),
 | 
			
		||||
    0 4px 6px -4px rgba($base-shadow-color, 0.25);
 | 
			
		||||
  cursor: default;
 | 
			
		||||
  transition: 0.5s cubic-bezier(0.89, 0.01, 0.5, 1.1);
 | 
			
		||||
  transform: translateZ(0);
 | 
			
		||||
  font-size: 15px;
 | 
			
		||||
  line-height: 21px;
 | 
			
		||||
 | 
			
		||||
  &.notification-bar-active {
 | 
			
		||||
    inset-inline-start: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification-bar-title {
 | 
			
		||||
  margin-inline-end: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification-bar-title,
 | 
			
		||||
.notification-bar-action {
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification-bar-action {
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  margin-inline-start: 10px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: $highlight-text-color;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  padding: 0 4px;
 | 
			
		||||
 | 
			
		||||
  &:hover,
 | 
			
		||||
  &:focus,
 | 
			
		||||
  &:active {
 | 
			
		||||
    background: rgba($ui-base-color, 0.85);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,14 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
 | 
			
		||||
  def perform
 | 
			
		||||
    return if skip_reports?
 | 
			
		||||
 | 
			
		||||
    target_accounts            = object_uris.filter_map { |uri| account_from_uri(uri) }.select(&:local?)
 | 
			
		||||
    target_statuses_by_account = object_uris.filter_map { |uri| status_from_uri(uri) }.select(&:local?).group_by(&:account_id)
 | 
			
		||||
    target_accounts            = object_uris.filter_map { |uri| account_from_uri(uri) }
 | 
			
		||||
    target_statuses_by_account = object_uris.filter_map { |uri| status_from_uri(uri) }.group_by(&:account_id)
 | 
			
		||||
 | 
			
		||||
    target_accounts.each do |target_account|
 | 
			
		||||
      target_statuses = target_statuses_by_account[target_account.id]
 | 
			
		||||
      target_statuses     = target_statuses_by_account[target_account.id]
 | 
			
		||||
      replied_to_accounts = Account.local.where(id: target_statuses.filter_map(&:in_reply_to_account_id))
 | 
			
		||||
 | 
			
		||||
      next if target_account.suspended?
 | 
			
		||||
      next if target_account.suspended? || (!target_account.local? && replied_to_accounts.none?)
 | 
			
		||||
 | 
			
		||||
      ReportService.new.call(
 | 
			
		||||
        @account,
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ class InitialStateSerializer < ActiveModel::Serializer
 | 
			
		||||
      limited_federation_mode: Rails.configuration.x.whitelist_mode,
 | 
			
		||||
      mascot: instance_presenter.mascot&.file&.url,
 | 
			
		||||
      profile_directory: Setting.profile_directory,
 | 
			
		||||
      trends: Setting.trends,
 | 
			
		||||
      trends_enabled: Setting.trends,
 | 
			
		||||
      registrations_open: Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode,
 | 
			
		||||
      timeline_preview: Setting.timeline_preview,
 | 
			
		||||
      activity_api_enabled: Setting.activity_api_enabled,
 | 
			
		||||
@@ -62,9 +62,9 @@ class InitialStateSerializer < ActiveModel::Serializer
 | 
			
		||||
      store[:advanced_layout]   = object.current_account.user.setting_advanced_layout
 | 
			
		||||
      store[:use_blurhash]      = object.current_account.user.setting_use_blurhash
 | 
			
		||||
      store[:use_pending_items] = object.current_account.user.setting_use_pending_items
 | 
			
		||||
      store[:trends]            = Setting.trends && object.current_account.user.setting_trends
 | 
			
		||||
      store[:default_content_type] = object.current_account.user.setting_default_content_type
 | 
			
		||||
      store[:system_emoji_font] = object.current_account.user.setting_system_emoji_font
 | 
			
		||||
      store[:show_trends]       = Setting.trends && object.current_account.user.setting_trends
 | 
			
		||||
      store[:crop_images]       = object.current_account.user.setting_crop_images
 | 
			
		||||
    else
 | 
			
		||||
      store[:auto_play_gif] = Setting.auto_play_gif
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,15 @@ class ReportService < BaseService
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def forward_to_origin!
 | 
			
		||||
    ActivityPub::DeliveryWorker.perform_async(
 | 
			
		||||
      payload,
 | 
			
		||||
      some_local_account.id,
 | 
			
		||||
      @target_account.inbox_url
 | 
			
		||||
    )
 | 
			
		||||
    # Send report to the server where the account originates from
 | 
			
		||||
    ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, @target_account.inbox_url)
 | 
			
		||||
 | 
			
		||||
    # Send report to servers to which the account was replying to, so they also have a chance to act
 | 
			
		||||
    inbox_urls = Account.remote.where(id: Status.where(id: reported_status_ids).where.not(in_reply_to_account_id: nil).select(:in_reply_to_account_id)).inboxes - [@target_account.inbox_url]
 | 
			
		||||
 | 
			
		||||
    inbox_urls.each do |inbox_url|
 | 
			
		||||
      ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def forward?
 | 
			
		||||
 
 | 
			
		||||
@@ -4,21 +4,25 @@ class FeedInsertWorker
 | 
			
		||||
  include Sidekiq::Worker
 | 
			
		||||
 | 
			
		||||
  def perform(status_id, id, type = 'home', options = {})
 | 
			
		||||
    @type      = type.to_sym
 | 
			
		||||
    @status    = Status.find(status_id)
 | 
			
		||||
    @options   = options.symbolize_keys
 | 
			
		||||
    ApplicationRecord.connected_to(role: :primary) do
 | 
			
		||||
      @type      = type.to_sym
 | 
			
		||||
      @status    = Status.find(status_id)
 | 
			
		||||
      @options   = options.symbolize_keys
 | 
			
		||||
 | 
			
		||||
    case @type
 | 
			
		||||
    when :home, :tags
 | 
			
		||||
      @follower = Account.find(id)
 | 
			
		||||
    when :list
 | 
			
		||||
      @list     = List.find(id)
 | 
			
		||||
      @follower = @list.account
 | 
			
		||||
    when :direct
 | 
			
		||||
      @account  = Account.find(id)
 | 
			
		||||
      case @type
 | 
			
		||||
      when :home, :tags
 | 
			
		||||
        @follower = Account.find(id)
 | 
			
		||||
      when :list
 | 
			
		||||
        @list     = List.find(id)
 | 
			
		||||
        @follower = @list.account
 | 
			
		||||
      when :direct
 | 
			
		||||
        @account  = Account.find(id)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    check_and_insert
 | 
			
		||||
    ApplicationRecord.connected_to(role: :read, prevent_writes: true) do
 | 
			
		||||
      check_and_insert
 | 
			
		||||
    end
 | 
			
		||||
  rescue ActiveRecord::RecordNotFound
 | 
			
		||||
    true
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user