mirror of
https://github.com/mastodon/mastodon.git
synced 2025-10-04 16:17:30 +00:00
Remove the outgoing_quotes
feature flag, making the feature unconditional (#36130)
This commit is contained in:
parent
6cbc857ee0
commit
e1f7847b64
|
@ -4,7 +4,6 @@ class Api::V1::Statuses::InteractionPoliciesController < Api::V1::Statuses::Base
|
|||
include Api::InteractionPoliciesConcern
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
|
||||
before_action -> { check_feature_enabled }
|
||||
|
||||
def update
|
||||
authorize @status, :update?
|
||||
|
@ -22,10 +21,6 @@ class Api::V1::Statuses::InteractionPoliciesController < Api::V1::Statuses::Base
|
|||
params.permit(:quote_approval_policy)
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.outgoing_quotes_enabled?
|
||||
end
|
||||
|
||||
def broadcast_updates!
|
||||
DistributionWorker.perform_async(@status.id, { 'update' => true })
|
||||
ActivityPub::StatusUpdateDistributionWorker.perform_async(@status.id, { 'updated_at' => Time.now.utc.iso8601 })
|
||||
|
|
|
@ -157,8 +157,6 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
end
|
||||
|
||||
def set_quoted_status
|
||||
return unless Mastodon::Feature.outgoing_quotes_enabled?
|
||||
|
||||
@quoted_status = Status.find(status_params[:quoted_status_id]) if status_params[:quoted_status_id].present?
|
||||
authorize(@quoted_status, :quote?) if @quoted_status.present?
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
|
|
|
@ -4,8 +4,6 @@ module Api::InteractionPoliciesConcern
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
def quote_approval_policy
|
||||
return nil unless Mastodon::Feature.outgoing_quotes_enabled?
|
||||
|
||||
case status_params[:quote_approval_policy].presence || current_user.setting_default_quote_policy
|
||||
when 'public'
|
||||
Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
|
|||
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
||||
import { statusFactoryState } from '@/testing/factories';
|
||||
|
||||
import { LegacyReblogButton, StatusBoostButton } from './boost_button';
|
||||
import { BoostButton } from './boost_button';
|
||||
|
||||
interface StoryProps {
|
||||
visibility: StatusVisibility;
|
||||
|
@ -38,10 +38,7 @@ const meta = {
|
|||
},
|
||||
},
|
||||
render: (args) => (
|
||||
<StatusBoostButton
|
||||
status={argsToStatus(args)}
|
||||
counters={args.reblogCount > 0}
|
||||
/>
|
||||
<BoostButton status={argsToStatus(args)} counters={args.reblogCount > 0} />
|
||||
),
|
||||
} satisfies Meta<StoryProps>;
|
||||
|
||||
|
@ -78,12 +75,3 @@ export const Mine: Story = {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Legacy: Story = {
|
||||
render: (args) => (
|
||||
<LegacyReblogButton
|
||||
status={argsToStatus(args)}
|
||||
counters={args.reblogCount > 0}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import type { FC, KeyboardEvent, MouseEvent, MouseEventHandler } from 'react';
|
||||
import type { FC, KeyboardEvent, MouseEvent } from 'react';
|
||||
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
|
@ -11,7 +11,6 @@ import { openModal } from '@/mastodon/actions/modal';
|
|||
import type { ActionMenuItem } from '@/mastodon/models/dropdown_menu';
|
||||
import type { Status } from '@/mastodon/models/status';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import { isFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import type { SomeRequired } from '@/mastodon/utils/types';
|
||||
|
||||
import type { RenderItemFn, RenderItemFnHandlers } from '../dropdown_menu';
|
||||
|
@ -47,10 +46,7 @@ interface ReblogButtonProps {
|
|||
|
||||
type ActionMenuItemWithIcon = SomeRequired<ActionMenuItem, 'icon'>;
|
||||
|
||||
export const StatusBoostButton: FC<ReblogButtonProps> = ({
|
||||
status,
|
||||
counters,
|
||||
}) => {
|
||||
export const BoostButton: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const statusState = useAppSelector((state) =>
|
||||
|
@ -192,65 +188,3 @@ const ReblogMenuItem: FC<ReblogMenuItemProps> = ({
|
|||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
// Legacy helpers
|
||||
|
||||
// Switch between the legacy and new reblog button based on feature flag.
|
||||
export const BoostButton: FC<ReblogButtonProps> = (props) => {
|
||||
if (isFeatureEnabled('outgoing_quotes')) {
|
||||
return <StatusBoostButton {...props} />;
|
||||
}
|
||||
return <LegacyReblogButton {...props} />;
|
||||
};
|
||||
|
||||
export const LegacyReblogButton: FC<ReblogButtonProps> = ({
|
||||
status,
|
||||
counters,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const statusState = useAppSelector((state) =>
|
||||
selectStatusState(state, status),
|
||||
);
|
||||
|
||||
const { title, meta, iconComponent, disabled } = useMemo(
|
||||
() => boostItemState(statusState),
|
||||
[statusState],
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleClick: MouseEventHandler = useCallback(
|
||||
(event) => {
|
||||
if (statusState.isLoggedIn) {
|
||||
dispatch(toggleReblog(status.get('id') as string, event.shiftKey));
|
||||
} else {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'INTERACTION',
|
||||
modalProps: {
|
||||
accountId: status.getIn(['account', 'id']),
|
||||
url: status.get('uri'),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch, status, statusState.isLoggedIn],
|
||||
);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
active={!!status.get('reblogged')}
|
||||
title={intl.formatMessage(meta ?? title)}
|
||||
icon='retweet'
|
||||
iconComponent={iconComponent}
|
||||
onClick={!disabled ? handleClick : undefined}
|
||||
counter={
|
||||
counters
|
||||
? (status.get('reblogs_count') as number) +
|
||||
(status.get('quotes_count') as number)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,7 +23,6 @@ import { Dropdown } from 'mastodon/components/dropdown_menu';
|
|||
import { me } from '../../initial_state';
|
||||
|
||||
import { IconButton } from '../icon_button';
|
||||
import { isFeatureEnabled } from '../../utils/environment';
|
||||
import { BoostButton } from '../status/boost_button';
|
||||
import { RemoveQuoteHint } from './remove_quote_hint';
|
||||
|
||||
|
@ -281,7 +280,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
if (writtenByMe || withDismiss) {
|
||||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
if (writtenByMe && isFeatureEnabled('outgoing_quotes') && !['private', 'direct'].includes(status.get('visibility'))) {
|
||||
if (writtenByMe && !['private', 'direct'].includes(status.get('visibility'))) {
|
||||
menu.push({ text: intl.formatMessage(messages.quotePolicyChange), action: this.handleQuotePolicyChange });
|
||||
}
|
||||
menu.push(null);
|
||||
|
|
|
@ -47,8 +47,6 @@ import Status from '../components/status';
|
|||
import { deleteModal } from '../initial_state';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
|
||||
|
||||
import { isFeatureEnabled } from 'mastodon/utils/environment';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
const getPictureInPicture = makeGetPictureInPicture();
|
||||
|
@ -81,9 +79,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
|
|||
},
|
||||
|
||||
onQuote (status) {
|
||||
if (isFeatureEnabled('outgoing_quotes')) {
|
||||
dispatch(quoteComposeById(status.get('id')));
|
||||
}
|
||||
dispatch(quoteComposeById(status.get('id')));
|
||||
},
|
||||
|
||||
onFavourite (status) {
|
||||
|
|
|
@ -12,14 +12,12 @@ import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes';
|
|||
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
|
||||
import { isFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react';
|
||||
|
||||
import type { VisibilityModalCallback } from '../../ui/components/visibility_modal';
|
||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||
|
||||
import { messages as privacyMessages } from './privacy_dropdown';
|
||||
|
||||
|
@ -43,9 +41,6 @@ interface PrivacyDropdownProps {
|
|||
}
|
||||
|
||||
export const VisibilityButton: FC<PrivacyDropdownProps> = (props) => {
|
||||
if (!isFeatureEnabled('outgoing_quotes')) {
|
||||
return <PrivacyDropdownContainer {...props} />;
|
||||
}
|
||||
return <PrivacyModalButton {...props} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { isFeatureEnabled } from 'mastodon/utils/environment';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
|
||||
|
@ -63,12 +62,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
<td><kbd>b</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.boost' defaultMessage='to boost' /></td>
|
||||
</tr>
|
||||
{isFeatureEnabled('outgoing_quotes') && (
|
||||
<tr>
|
||||
<td><kbd>q</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.quote' defaultMessage='Quote post' /></td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td><kbd>q</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.quote' defaultMessage='Quote post' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>enter</kbd>, <kbd>o</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
|
||||
|
|
|
@ -19,7 +19,6 @@ import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/
|
|||
import { IconButton } from '../../../components/icon_button';
|
||||
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
||||
import { me } from '../../../initial_state';
|
||||
import { isFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { BoostButton } from '@/mastodon/components/status/boost_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -237,7 +236,7 @@ class ActionBar extends PureComponent {
|
|||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
if (isFeatureEnabled('outgoing_quotes') && !['private', 'direct'].includes(status.get('visibility'))) {
|
||||
if (!['private', 'direct'].includes(status.get('visibility'))) {
|
||||
menu.push({ text: intl.formatMessage(messages.quotePolicyChange), action: this.handleQuotePolicyChange });
|
||||
}
|
||||
menu.push(null);
|
||||
|
|
|
@ -12,11 +12,7 @@ export function isProduction() {
|
|||
else return import.meta.env.PROD;
|
||||
}
|
||||
|
||||
export type Features =
|
||||
| 'modern_emojis'
|
||||
| 'outgoing_quotes'
|
||||
| 'fasp'
|
||||
| 'http_message_signatures';
|
||||
export type Features = 'modern_emojis' | 'fasp' | 'http_message_signatures';
|
||||
|
||||
export function isFeatureEnabled(feature: Features) {
|
||||
return initialState?.features.includes(feature) ?? false;
|
||||
|
|
|
@ -9,7 +9,7 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity
|
|||
quoted_status = status_from_uri(object_uri)
|
||||
return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable?
|
||||
|
||||
if Mastodon::Feature.outgoing_quotes_enabled? && StatusPolicy.new(@account, quoted_status).quote?
|
||||
if StatusPolicy.new(@account, quoted_status).quote?
|
||||
accept_quote_request!(quoted_status)
|
||||
else
|
||||
reject_quote_request!(quoted_status)
|
||||
|
|
|
@ -36,7 +36,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||
attribute :quote, key: :quote_uri, if: :quote?
|
||||
attribute :quote_authorization, if: :quote_authorization?
|
||||
|
||||
attribute :interaction_policy, if: -> { Mastodon::Feature.outgoing_quotes_enabled? }
|
||||
attribute :interaction_policy
|
||||
|
||||
def id
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
|
|
|
@ -6,5 +6,5 @@ class REST::ShallowStatusSerializer < REST::StatusSerializer
|
|||
# It looks like redefining one `has_one` requires redefining all inherited ones
|
||||
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
|
||||
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
|
||||
has_one :quote_approval, if: -> { Mastodon::Feature.outgoing_quotes_enabled? }
|
||||
has_one :quote_approval
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
has_one :quote, key: :quote, serializer: REST::QuoteSerializer
|
||||
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
|
||||
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
|
||||
has_one :quote_approval, if: -> { Mastodon::Feature.outgoing_quotes_enabled? }
|
||||
has_one :quote_approval
|
||||
|
||||
def quote
|
||||
object.quote if object.quote&.acceptable?
|
||||
|
|
|
@ -45,7 +45,7 @@ module Mastodon
|
|||
|
||||
def api_versions
|
||||
{
|
||||
mastodon: Mastodon::Feature.outgoing_quotes_enabled? ? 7 : 6,
|
||||
mastodon: 7,
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do
|
||||
RSpec.describe ActivityPub::Activity::QuoteRequest do
|
||||
let(:sender) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let(:quoted_post) { Fabricate(:status, account: recipient) }
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe StatusCacheHydrator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when handling a status with a quote policy', feature: :outgoing_quotes do
|
||||
context 'when handling a status with a quote policy' do
|
||||
let(:status) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) }
|
||||
|
||||
before do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Interaction policies', feature: :outgoing_quotes do
|
||||
RSpec.describe 'Interaction policies' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:scopes) { 'write:statuses' }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
|
|
|
@ -158,7 +158,7 @@ RSpec.describe '/api/v1/statuses' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'without a quote policy', feature: :outgoing_quotes do
|
||||
context 'without a quote policy' do
|
||||
let(:user) do
|
||||
Fabricate(:user, settings: { default_quote_policy: 'followers' })
|
||||
end
|
||||
|
@ -180,7 +180,7 @@ RSpec.describe '/api/v1/statuses' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'without a quote policy and the user defaults to nobody', feature: :outgoing_quotes do
|
||||
context 'without a quote policy and the user defaults to nobody' do
|
||||
let(:user) do
|
||||
Fabricate(:user, settings: { default_quote_policy: 'nobody' })
|
||||
end
|
||||
|
@ -202,7 +202,7 @@ RSpec.describe '/api/v1/statuses' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a quote policy', feature: :outgoing_quotes do
|
||||
context 'with a quote policy' do
|
||||
let(:quoted_status) { Fabricate(:status, account: user.account) }
|
||||
let(:params) do
|
||||
{
|
||||
|
@ -227,7 +227,7 @@ RSpec.describe '/api/v1/statuses' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a self-quote post', feature: :outgoing_quotes do
|
||||
context 'with a self-quote post' do
|
||||
let(:quoted_status) { Fabricate(:status, account: user.account) }
|
||||
let(:params) do
|
||||
{
|
||||
|
@ -248,7 +248,7 @@ RSpec.describe '/api/v1/statuses' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a self-quote post and a CW but no text', feature: :outgoing_quotes do
|
||||
context 'with a self-quote post and a CW but no text' do
|
||||
let(:quoted_status) { Fabricate(:status, account: user.account) }
|
||||
let(:params) do
|
||||
{
|
||||
|
@ -420,7 +420,7 @@ RSpec.describe '/api/v1/statuses' do
|
|||
context 'when updating only the quote policy' do
|
||||
let(:params) { { status: status.text, quote_approval_policy: 'public' } }
|
||||
|
||||
it 'updates the status', :aggregate_failures, feature: :outgoing_quotes do
|
||||
it 'updates the status', :aggregate_failures do
|
||||
expect { subject }
|
||||
.to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16)
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ RSpec.describe ActivityPub::NoteSerializer do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a quote policy', feature: :outgoing_quotes do
|
||||
context 'with a quote policy' do
|
||||
let(:parent) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) }
|
||||
|
||||
it 'has the expected shape' do
|
||||
|
|
Loading…
Reference in New Issue
Block a user