Merge pull request #3406 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to cff25c186b
This commit is contained in:
Claire
2026-02-17 12:10:41 +01:00
committed by GitHub
10 changed files with 38 additions and 26 deletions

View File

@@ -1,5 +1,5 @@
import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment'; import { isServerFeatureEnabled } from '@/flavours/glitch/utils/environment';
export function isRedesignEnabled() { export function isRedesignEnabled() {
return isClientFeatureEnabled('profile_redesign'); return isServerFeatureEnabled('profile_redesign');
} }

View File

@@ -25,6 +25,7 @@ import { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications'; import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { checkAnnualReport } from '@/flavours/glitch/reducers/slices/annual_report'; import { checkAnnualReport } from '@/flavours/glitch/reducers/slices/annual_report';
import { isServerFeatureEnabled } from '@/flavours/glitch/utils/environment';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
import { clearHeight } from '../../actions/height_cache'; import { clearHeight } from '../../actions/height_cache';
@@ -92,7 +93,6 @@ import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
// Without this it ends up in ~8 very commonly used bundles. // Without this it ends up in ~8 very commonly used bundles.
import '../../components/status'; import '../../components/status';
import { areCollectionsEnabled } from '../collections/utils'; import { areCollectionsEnabled } from '../collections/utils';
import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment';
const messages = defineMessages({ const messages = defineMessages({
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
@@ -170,7 +170,7 @@ class SwitchingColumnsArea extends PureComponent {
redirect = <Redirect from='/' to='/about' exact />; redirect = <Redirect from='/' to='/about' exact />;
} }
const profileRedesignEnabled = isClientFeatureEnabled('profile_redesign'); const profileRedesignEnabled = isServerFeatureEnabled('profile_redesign');
const profileRedesignRoutes = []; const profileRedesignRoutes = [];
if (profileRedesignEnabled) { if (profileRedesignEnabled) {
profileRedesignRoutes.push( profileRedesignRoutes.push(

View File

@@ -1,4 +1,4 @@
import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment'; import { isServerFeatureEnabled } from '@/flavours/glitch/utils/environment';
export function EmojiPicker () { export function EmojiPicker () {
return import('../../emoji/emoji_picker'); return import('../../emoji/emoji_picker');
@@ -73,7 +73,7 @@ export function PinnedStatuses () {
} }
export function AccountTimeline () { export function AccountTimeline () {
if (isClientFeatureEnabled('profile_redesign')) { if (isServerFeatureEnabled('profile_redesign')) {
return import('../../account_timeline/v2'); return import('../../account_timeline/v2');
} }
return import('../../account_timeline'); return import('../../account_timeline');

View File

@@ -12,13 +12,13 @@ export function isProduction() {
else return import.meta.env.PROD; else return import.meta.env.PROD;
} }
export type ServerFeatures = 'fasp' | 'collections'; export type ServerFeatures = 'fasp' | 'collections' | 'profile_redesign';
export function isServerFeatureEnabled(feature: ServerFeatures) { export function isServerFeatureEnabled(feature: ServerFeatures) {
return initialState?.features.includes(feature) ?? false; return initialState?.features.includes(feature) ?? false;
} }
type ClientFeatures = 'profile_redesign' | 'collections'; type ClientFeatures = 'collections';
export function isClientFeatureEnabled(feature: ClientFeatures) { export function isClientFeatureEnabled(feature: ClientFeatures) {
try { try {

View File

@@ -1,5 +1,5 @@
import { isClientFeatureEnabled } from '@/mastodon/utils/environment'; import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
export function isRedesignEnabled() { export function isRedesignEnabled() {
return isClientFeatureEnabled('profile_redesign'); return isServerFeatureEnabled('profile_redesign');
} }

View File

@@ -22,6 +22,7 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex
import { layoutFromWindow } from 'mastodon/is_mobile'; import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { checkAnnualReport } from '@/mastodon/reducers/slices/annual_report'; import { checkAnnualReport } from '@/mastodon/reducers/slices/annual_report';
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
import { clearHeight } from '../../actions/height_cache'; import { clearHeight } from '../../actions/height_cache';
@@ -89,7 +90,6 @@ import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
// Without this it ends up in ~8 very commonly used bundles. // Without this it ends up in ~8 very commonly used bundles.
import '../../components/status'; import '../../components/status';
import { areCollectionsEnabled } from '../collections/utils'; import { areCollectionsEnabled } from '../collections/utils';
import { isClientFeatureEnabled } from '@/mastodon/utils/environment';
const messages = defineMessages({ const messages = defineMessages({
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
@@ -162,7 +162,7 @@ class SwitchingColumnsArea extends PureComponent {
redirect = <Redirect from='/' to='/about' exact />; redirect = <Redirect from='/' to='/about' exact />;
} }
const profileRedesignEnabled = isClientFeatureEnabled('profile_redesign'); const profileRedesignEnabled = isServerFeatureEnabled('profile_redesign');
const profileRedesignRoutes = []; const profileRedesignRoutes = [];
if (profileRedesignEnabled) { if (profileRedesignEnabled) {
profileRedesignRoutes.push( profileRedesignRoutes.push(

View File

@@ -1,4 +1,4 @@
import { isClientFeatureEnabled } from '@/mastodon/utils/environment'; import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
export function EmojiPicker () { export function EmojiPicker () {
return import('../../emoji/emoji_picker'); return import('../../emoji/emoji_picker');
@@ -73,7 +73,7 @@ export function PinnedStatuses () {
} }
export function AccountTimeline () { export function AccountTimeline () {
if (isClientFeatureEnabled('profile_redesign')) { if (isServerFeatureEnabled('profile_redesign')) {
return import('../../account_timeline/v2'); return import('../../account_timeline/v2');
} }
return import('../../account_timeline'); return import('../../account_timeline');

View File

@@ -12,13 +12,13 @@ export function isProduction() {
else return import.meta.env.PROD; else return import.meta.env.PROD;
} }
export type ServerFeatures = 'fasp' | 'collections'; export type ServerFeatures = 'fasp' | 'collections' | 'profile_redesign';
export function isServerFeatureEnabled(feature: ServerFeatures) { export function isServerFeatureEnabled(feature: ServerFeatures) {
return initialState?.features.includes(feature) ?? false; return initialState?.features.includes(feature) ?? false;
} }
type ClientFeatures = 'profile_redesign' | 'collections'; type ClientFeatures = 'collections';
export function isClientFeatureEnabled(feature: ClientFeatures) { export function isClientFeatureEnabled(feature: ClientFeatures) {
try { try {

View File

@@ -2,6 +2,7 @@
class PostStatusService < BaseService class PostStatusService < BaseService
include Redisable include Redisable
include Lockable
include LanguagesHelper include LanguagesHelper
class UnexpectedMentionsError < StandardError class UnexpectedMentionsError < StandardError
@@ -39,19 +40,17 @@ class PostStatusService < BaseService
@in_reply_to = @options[:thread] @in_reply_to = @options[:thread]
@quoted_status = @options[:quoted_status] @quoted_status = @options[:quoted_status]
return idempotency_duplicate if idempotency_given? && idempotency_duplicate? with_idempotency do
validate_media!
preprocess_attributes!
validate_media! if scheduled?
preprocess_attributes! schedule_status!
else
if scheduled? process_status!
schedule_status! end
else
process_status!
end end
redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given?
unless scheduled? unless scheduled?
postprocess_status! postprocess_status!
bump_potential_friendship! bump_potential_friendship!
@@ -224,6 +223,18 @@ class PostStatusService < BaseService
@idempotency_duplicate = redis.get(idempotency_key) @idempotency_duplicate = redis.get(idempotency_key)
end end
def with_idempotency
return yield unless idempotency_given?
with_redis_lock("idempotency:lock:status:#{@account.id}:#{@options[:idempotency]}") do
return idempotency_duplicate if idempotency_duplicate?
yield
redis.setex(idempotency_key, 3_600, @status.id)
end
end
def scheduled_in_the_past? def scheduled_in_the_past?
@scheduled_at.present? && @scheduled_at <= Time.now.utc @scheduled_at.present? && @scheduled_at <= Time.now.utc
end end

View File

@@ -224,6 +224,7 @@ en:
email: E-mail address email: E-mail address
expires_in: Expire after expires_in: Expire after
fields: Extra fields fields: Extra fields
filter_action: Filter action
header: Header picture header: Header picture
honeypot: "%{label} (do not fill in)" honeypot: "%{label} (do not fill in)"
inbox_url: URL of the relay inbox inbox_url: URL of the relay inbox