Merge commit 'a32a126cac42c73236236b5a9bd660765b9c58ee' into glitch-soc/merge-upstream

Conflicts:
- `spec/lib/sanitize/config_spec.rb`:
  Conflict due to glitch-soc having factored the file differently.
  Ported upstream's changes.
shrike
Claire 2024-03-13 20:14:18 +01:00
commit 65ca37bbaa
19 changed files with 109 additions and 64 deletions

View File

@ -131,7 +131,7 @@ class ApplicationController < ActionController::Base
end
def single_user_mode?
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.without_internal.exists?
end
def use_seamless_external_login?

View File

@ -214,7 +214,7 @@ module ApplicationHelper
state_params[:moved_to_account] = current_account.moved_to_account
end
state_params[:owner] = Account.local.without_suspended.where('id > 0').first if single_user_mode?
state_params[:owner] = Account.local.without_suspended.without_internal.first if single_user_mode?
json = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(state_params), serializer: InitialStateSerializer).to_json
# rubocop:disable Rails/OutputSafety

View File

@ -22,6 +22,7 @@ import Card from '../features/status/components/card';
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context';
import { displayMedia } from '../initial_state';
import { Avatar } from './avatar';
@ -78,6 +79,8 @@ const messages = defineMessages({
class Status extends ImmutablePureComponent {
static contextType = SensitiveMediaContext;
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.record,
@ -133,19 +136,21 @@ class Status extends ImmutablePureComponent {
];
state = {
showMedia: defaultMediaVisibility(this.props.status),
statusId: undefined,
showMedia: defaultMediaVisibility(this.props.status) && !(this.context?.hideMediaByDefault),
forceFilter: undefined,
};
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return {
showMedia: defaultMediaVisibility(nextProps.status),
statusId: nextProps.status.get('id'),
};
} else {
return null;
componentDidUpdate (prevProps) {
// This will potentially cause a wasteful redraw, but in most cases `Status` components are used
// with a `key` directly depending on their `id`, preventing re-use of the component across
// different IDs.
// But just in case this does change, reset the state on status change.
if (this.props.status?.get('id') !== prevProps.status?.get('id')) {
this.setState({
showMedia: defaultMediaVisibility(this.props.status) && !(this.context?.hideMediaByDefault),
forceFilter: undefined,
});
}
}

View File

@ -15,6 +15,7 @@ import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { IconButton } from 'mastodon/components/icon_button';
import ScrollableList from 'mastodon/components/scrollable_list';
import { SensitiveMediaContextProvider } from 'mastodon/features/ui/util/sensitive_media_context';
import NotificationContainer from './containers/notification_container';
@ -106,25 +107,27 @@ export const NotificationRequest = ({ multiColumn, params: { id } }) => {
)}
/>
<ScrollableList
scrollKey={`notification_requests/${id}`}
trackScroll={!multiColumn}
bindToDocument={!multiColumn}
isLoading={isLoading}
showLoading={isLoading && notifications.size === 0}
hasMore={hasMore}
onLoadMore={handleLoadMore}
>
{notifications.map(item => (
item && <NotificationContainer
key={item.get('id')}
notification={item}
accountId={item.get('account')}
onMoveUp={handleMoveUp}
onMoveDown={handleMoveDown}
/>
))}
</ScrollableList>
<SensitiveMediaContextProvider hideMediaByDefault>
<ScrollableList
scrollKey={`notification_requests/${id}`}
trackScroll={!multiColumn}
bindToDocument={!multiColumn}
isLoading={isLoading}
showLoading={isLoading && notifications.size === 0}
hasMore={hasMore}
onLoadMore={handleLoadMore}
>
{notifications.map(item => (
item && <NotificationContainer
key={item.get('id')}
notification={item}
accountId={item.get('account')}
onMoveUp={handleMoveUp}
onMoveDown={handleMoveDown}
/>
))}
</ScrollableList>
</SensitiveMediaContextProvider>
<Helmet>
<title>{columnTitle}</title>

View File

@ -0,0 +1,28 @@
import { createContext, useContext, useMemo } from 'react';
export const SensitiveMediaContext = createContext<{
hideMediaByDefault: boolean;
}>({
hideMediaByDefault: false,
});
export function useSensitiveMediaContext() {
return useContext(SensitiveMediaContext);
}
type ContextValue = React.ContextType<typeof SensitiveMediaContext>;
export const SensitiveMediaContextProvider: React.FC<
React.PropsWithChildren<{ hideMediaByDefault: boolean }>
> = ({ hideMediaByDefault, children }) => {
const contextValue = useMemo<ContextValue>(
() => ({ hideMediaByDefault }),
[hideMediaByDefault],
);
return (
<SensitiveMediaContext.Provider value={contextValue}>
{children}
</SensitiveMediaContext.Provider>
);
};

View File

@ -115,6 +115,7 @@ class Account < ApplicationRecord
normalizes :username, with: ->(username) { username.squish }
scope :without_internal, -> { where(id: 1...) }
scope :remote, -> { where.not(domain: nil) }
scope :local, -> { where(domain: nil) }
scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }

View File

@ -41,6 +41,14 @@ RSpec.describe InstanceActorsController do
it_behaves_like 'shared behavior'
end
context 'with a suspended instance actor' do
let(:authorized_fetch_mode) { false }
before { Account.representative.update(suspended_at: 10.days.ago) }
it_behaves_like 'shared behavior'
end
end
end
end

View File

@ -11,7 +11,7 @@ RSpec.describe FeedManager do
end
it 'tracks at least as many statuses as reblogs', :skip_stub do
expect(FeedManager::REBLOG_FALLOFF).to be <= FeedManager::MAX_ITEMS
expect(described_class::REBLOG_FALLOFF).to be <= described_class::MAX_ITEMS
end
describe '#key' do
@ -232,12 +232,12 @@ RSpec.describe FeedManager do
it 'trims timelines if they will have more than FeedManager::MAX_ITEMS' do
account = Fabricate(:account)
status = Fabricate(:status)
members = Array.new(FeedManager::MAX_ITEMS) { |count| [count, count] }
members = Array.new(described_class::MAX_ITEMS) { |count| [count, count] }
redis.zadd("feed:home:#{account.id}", members)
described_class.instance.push_to_home(account, status)
expect(redis.zcard("feed:home:#{account.id}")).to eq FeedManager::MAX_ITEMS
expect(redis.zcard("feed:home:#{account.id}")).to eq described_class::MAX_ITEMS
end
context 'with reblogs' do
@ -267,7 +267,7 @@ RSpec.describe FeedManager do
described_class.instance.push_to_home(account, reblogged)
# Fill the feed with intervening statuses
FeedManager::REBLOG_FALLOFF.times do
described_class::REBLOG_FALLOFF.times do
described_class.instance.push_to_home(account, Fabricate(:status))
end
@ -328,7 +328,7 @@ RSpec.describe FeedManager do
described_class.instance.push_to_home(account, reblogs.first)
# Fill the feed with intervening statuses
FeedManager::REBLOG_FALLOFF.times do
described_class::REBLOG_FALLOFF.times do
described_class.instance.push_to_home(account, Fabricate(:status))
end
@ -474,7 +474,7 @@ RSpec.describe FeedManager do
status = Fabricate(:status, reblog: reblogged)
described_class.instance.push_to_home(receiver, reblogged)
FeedManager::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
described_class::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
described_class.instance.push_to_home(receiver, status)
# The reblogging status should show up under normal conditions.

View File

@ -58,7 +58,7 @@ describe Sanitize::Config do
end
describe '::MASTODON_OUTGOING' do
subject { Sanitize::Config::MASTODON_OUTGOING }
subject { described_class::MASTODON_OUTGOING }
around do |example|
original_web_domain = Rails.configuration.x.web_domain

View File

@ -27,7 +27,7 @@ RSpec.describe SignatureParser do
let(:header) { 'hello this is malformed!' }
it 'raises an error' do
expect { subject }.to raise_error(SignatureParser::ParsingError)
expect { subject }.to raise_error(described_class::ParsingError)
end
end
end

View File

@ -46,7 +46,7 @@ describe WebfingerResource do
expect do
described_class.new(resource).username
end.to raise_error(WebfingerResource::InvalidRequest)
end.to raise_error(described_class::InvalidRequest)
end
it 'finds the username in a valid https route' do
@ -137,7 +137,7 @@ describe WebfingerResource do
expect do
described_class.new(resource).username
end.to raise_error(WebfingerResource::InvalidRequest)
end.to raise_error(described_class::InvalidRequest)
end
end
end

View File

@ -678,7 +678,7 @@ RSpec.describe Account do
end
describe 'MENTION_RE' do
subject { Account::MENTION_RE }
subject { described_class::MENTION_RE }
it 'matches usernames in the middle of a sentence' do
expect(subject.match('Hello to @alice from me')[1]).to eq 'alice'
@ -888,7 +888,7 @@ RSpec.describe Account do
{ username: 'b', domain: 'b' },
].map(&method(:Fabricate).curry(2).call(:account))
expect(described_class.where('id > 0').alphabetic).to eq matches
expect(described_class.without_internal.alphabetic).to eq matches
end
end
@ -939,7 +939,7 @@ RSpec.describe Account do
it 'returns an array of accounts who do not have a domain' do
local_account = Fabricate(:account, domain: nil)
_account_with_domain = Fabricate(:account, domain: 'example.com')
expect(described_class.where('id > 0').local).to contain_exactly(local_account)
expect(described_class.without_internal.local).to contain_exactly(local_account)
end
end
@ -950,14 +950,14 @@ RSpec.describe Account do
matches[index] = Fabricate(:account, domain: matches[index])
end
expect(described_class.where('id > 0').partitioned).to match_array(matches)
expect(described_class.without_internal.partitioned).to match_array(matches)
end
end
describe 'recent' do
it 'returns a relation of accounts sorted by recent creation' do
matches = Array.new(2) { Fabricate(:account) }
expect(described_class.where('id > 0').recent).to match_array(matches)
expect(described_class.without_internal.recent).to match_array(matches)
end
end

View File

@ -30,7 +30,7 @@ RSpec.describe Form::Import do
it 'has errors' do
subject.validate
expect(subject.errors[:data]).to include(I18n.t('imports.errors.over_rows_processing_limit', count: Form::Import::ROWS_PROCESSING_LIMIT))
expect(subject.errors[:data]).to include(I18n.t('imports.errors.over_rows_processing_limit', count: described_class::ROWS_PROCESSING_LIMIT))
end
end

View File

@ -8,7 +8,7 @@ describe PrivacyPolicy do
it 'has the privacy text' do
policy = described_class.current
expect(policy.text).to eq(PrivacyPolicy::DEFAULT_PRIVACY_POLICY)
expect(policy.text).to eq(described_class::DEFAULT_PRIVACY_POLICY)
end
end

View File

@ -22,7 +22,7 @@ RSpec.describe Tag do
end
describe 'HASHTAG_RE' do
subject { Tag::HASHTAG_RE }
subject { described_class::HASHTAG_RE }
it 'does not match URLs with anchors with non-hashtag characters' do
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil

View File

@ -8,7 +8,7 @@ RSpec.describe UserRole do
describe '#can?' do
context 'with a single flag' do
it 'returns true if any of them are present' do
subject.permissions = UserRole::FLAGS[:manage_reports]
subject.permissions = described_class::FLAGS[:manage_reports]
expect(subject.can?(:manage_reports)).to be true
end
@ -19,7 +19,7 @@ RSpec.describe UserRole do
context 'with multiple flags' do
it 'returns true if any of them are present' do
subject.permissions = UserRole::FLAGS[:manage_users]
subject.permissions = described_class::FLAGS[:manage_users]
expect(subject.can?(:manage_reports, :manage_users)).to be true
end
@ -51,7 +51,7 @@ RSpec.describe UserRole do
describe '#permissions_as_keys' do
before do
subject.permissions = UserRole::FLAGS[:invite_users] | UserRole::FLAGS[:view_dashboard] | UserRole::FLAGS[:manage_reports]
subject.permissions = described_class::FLAGS[:invite_users] | described_class::FLAGS[:view_dashboard] | described_class::FLAGS[:manage_reports]
end
it 'returns an array' do
@ -70,7 +70,7 @@ RSpec.describe UserRole do
let(:input) { %w(manage_users) }
it 'sets permission flags' do
expect(subject.permissions).to eq UserRole::FLAGS[:manage_users]
expect(subject.permissions).to eq described_class::FLAGS[:manage_users]
end
end
@ -78,7 +78,7 @@ RSpec.describe UserRole do
let(:input) { %w(manage_users manage_reports) }
it 'sets permission flags' do
expect(subject.permissions).to eq UserRole::FLAGS[:manage_users] | UserRole::FLAGS[:manage_reports]
expect(subject.permissions).to eq described_class::FLAGS[:manage_users] | described_class::FLAGS[:manage_reports]
end
end
@ -86,7 +86,7 @@ RSpec.describe UserRole do
let(:input) { %w(foo) }
it 'does not set permission flags' do
expect(subject.permissions).to eq UserRole::Flags::NONE
expect(subject.permissions).to eq described_class::Flags::NONE
end
end
end
@ -96,7 +96,7 @@ RSpec.describe UserRole do
subject { described_class.nobody }
it 'returns none' do
expect(subject.computed_permissions).to eq UserRole::Flags::NONE
expect(subject.computed_permissions).to eq described_class::Flags::NONE
end
end
@ -110,11 +110,11 @@ RSpec.describe UserRole do
context 'when role has the administrator flag' do
before do
subject.permissions = UserRole::FLAGS[:administrator]
subject.permissions = described_class::FLAGS[:administrator]
end
it 'returns all permissions' do
expect(subject.computed_permissions).to eq UserRole::Flags::ALL
expect(subject.computed_permissions).to eq described_class::Flags::ALL
end
end
@ -135,7 +135,7 @@ RSpec.describe UserRole do
end
it 'has default permissions' do
expect(subject.permissions).to eq UserRole::FLAGS[:invite_users]
expect(subject.permissions).to eq described_class::FLAGS[:invite_users]
end
it 'has negative position' do
@ -155,7 +155,7 @@ RSpec.describe UserRole do
end
it 'has no permissions' do
expect(subject.permissions).to eq UserRole::Flags::NONE
expect(subject.permissions).to eq described_class::Flags::NONE
end
it 'has negative position' do

View File

@ -24,7 +24,7 @@ RSpec.describe UserSettings do
context 'when setting was not defined' do
it 'raises error' do
expect { subject[:foo] }.to raise_error UserSettings::KeyError
expect { subject[:foo] }.to raise_error described_class::KeyError
end
end
end
@ -93,7 +93,7 @@ RSpec.describe UserSettings do
describe '.definition_for' do
context 'when key is defined' do
it 'returns a setting' do
expect(described_class.definition_for(:always_send_emails)).to be_a UserSettings::Setting
expect(described_class.definition_for(:always_send_emails)).to be_a described_class::Setting
end
end

View File

@ -150,7 +150,7 @@ RSpec.describe PostStatusService do
expect do
subject.call(account, text: '@alice hm, @bob is really annoying lately', allowed_mentions: [mentioned_account.id])
end.to raise_error(an_instance_of(PostStatusService::UnexpectedMentionsError).and(having_attributes(accounts: [unexpected_mentioned_account])))
end.to raise_error(an_instance_of(described_class::UnexpectedMentionsError).and(having_attributes(accounts: [unexpected_mentioned_account])))
end
it 'processes duplicate mentions correctly' do

View File

@ -56,7 +56,7 @@ RSpec.describe FollowLimitValidator do
follow.valid?
expect(follow.errors[:base]).to include(I18n.t('users.follow_limit_reached', limit: FollowLimitValidator::LIMIT))
expect(follow.errors[:base]).to include(I18n.t('users.follow_limit_reached', limit: described_class::LIMIT))
end
end