Merge commit '1571514e49ec02a57c050612b3bca856f54933fb' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2025-09-27 21:29:19 +02:00
25 changed files with 395 additions and 111 deletions

View File

@@ -345,7 +345,7 @@ GEM
azure-blob (~> 0.5.2)
hashie (~> 5.0)
jmespath (1.6.2)
json (2.13.2)
json (2.15.0)
json-canonicalization (1.0.0)
json-jwt (1.16.7)
activesupport (>= 4.2)
@@ -626,7 +626,7 @@ GEM
net-smtp
premailer (~> 1.7, >= 1.7.9)
prettyprint (0.2.0)
prism (1.4.0)
prism (1.5.1)
prometheus_exporter (2.3.0)
webrick
propshaft (1.3.1)
@@ -722,7 +722,7 @@ GEM
redis (4.8.1)
redis-client (0.26.0)
connection_pool
regexp_parser (2.11.2)
regexp_parser (2.11.3)
reline (0.6.2)
io-console (~> 0.5)
request_store (1.7.0)
@@ -766,7 +766,7 @@ GEM
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 9)
rspec-support (3.13.4)
rubocop (1.80.2)
rubocop (1.81.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -774,10 +774,10 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.46.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.46.0)
rubocop-ast (1.47.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
@@ -901,9 +901,9 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.9.1)
unicode-display_width (3.1.5)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.1.0)
uri (1.0.3)
useragent (0.16.11)
validate_url (1.0.15)

View File

@@ -1,6 +1,7 @@
import type { PropsWithChildren } from 'react';
import type React from 'react';
import type { useLocation } from 'react-router';
import { Router as OriginalRouter, useHistory } from 'react-router';
import type {
@@ -18,7 +19,9 @@ interface MastodonLocationState {
mastodonModalKey?: string;
}
type LocationState = MastodonLocationState | null | undefined;
export type LocationState = MastodonLocationState | null | undefined;
export type MastodonLocation = ReturnType<typeof useLocation<LocationState>>;
type HistoryPath = Path | LocationDescriptor<LocationState>;

View File

@@ -10,7 +10,7 @@ import { connect } from 'react-redux';
import { supportsPassiveEvents } from 'detect-passive-events';
import { throttle } from 'lodash';
import ScrollContainer from 'mastodon/containers/scroll_container';
import { ScrollContainer } from 'mastodon/containers/scroll_container';
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
@@ -399,7 +399,7 @@ class ScrollableList extends PureComponent {
if (trackScroll) {
return (
<ScrollContainer scrollKey={scrollKey}>
<ScrollContainer scrollKey={scrollKey} childRef={this.setRef}>
{scrollableArea}
</ScrollContainer>
);

View File

@@ -5,7 +5,6 @@ import { Route } from 'react-router-dom';
import { Provider as ReduxProvider } from 'react-redux';
import { ScrollContext } from 'react-router-scroll-4';
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { hydrateStore } from 'mastodon/actions/store';
@@ -20,6 +19,8 @@ import { store } from 'mastodon/store';
import { isProduction } from 'mastodon/utils/environment';
import { BodyScrollLock } from 'mastodon/features/ui/components/body_scroll_lock';
import { ScrollContext } from './scroll_container/scroll_context';
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
const hydrateAction = hydrateStore(initialState);
@@ -45,10 +46,6 @@ export default class Mastodon extends PureComponent {
}
}
shouldUpdateScroll (prevRouterProps, { location }) {
return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey);
}
render () {
return (
<IdentityContext.Provider value={this.identity}>
@@ -56,7 +53,7 @@ export default class Mastodon extends PureComponent {
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<ScrollContext>
<Route path='/' component={UI} />
</ScrollContext>
<BodyScrollLock />

View File

@@ -1,18 +0,0 @@
import { ScrollContainer as OriginalScrollContainer } from 'react-router-scroll-4';
// ScrollContainer is used to automatically scroll to the top when pushing a
// new history state and remembering the scroll position when going back.
// There are a few things we need to do differently, though.
const defaultShouldUpdateScroll = (prevRouterProps, { location }) => {
// If the change is caused by opening a modal, do not scroll to top
return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey);
};
export default
class ScrollContainer extends OriginalScrollContainer {
static defaultProps = {
shouldUpdateScroll: defaultShouldUpdateScroll,
};
}

View File

@@ -0,0 +1,25 @@
import type { MastodonLocation } from 'mastodon/components/router';
export type ShouldUpdateScrollFn = (
prevLocationContext: MastodonLocation | null,
locationContext: MastodonLocation,
) => boolean;
/**
* ScrollBehavior will automatically scroll to the top on navigations
* or restore saved scroll positions, but on some location changes we
* need to prevent this.
*/
export const defaultShouldUpdateScroll: ShouldUpdateScrollFn = (
prevLocation,
location,
) => {
// If the change is caused by opening a modal, do not scroll to top
const shouldUpdateScroll = !(
location.state?.mastodonModalKey &&
location.state.mastodonModalKey !== prevLocation?.state?.mastodonModalKey
);
return shouldUpdateScroll;
};

View File

@@ -0,0 +1,76 @@
import React, {
useContext,
useEffect,
useImperativeHandle,
useRef,
} from 'react';
import { defaultShouldUpdateScroll } from './default_should_update_scroll';
import type { ShouldUpdateScrollFn } from './default_should_update_scroll';
import { ScrollBehaviorContext } from './scroll_context';
interface ScrollContainerProps {
/**
* This key must be static for the element & not change
* while the component is mounted.
*/
scrollKey: string;
shouldUpdateScroll?: ShouldUpdateScrollFn;
childRef?: React.ForwardedRef<HTMLElement | undefined>;
children: React.ReactElement;
}
/**
* `ScrollContainer` is used to manage the scroll position of elements on the page
* that can be scrolled independently of the page body.
* This component is a port of the unmaintained https://github.com/ytase/react-router-scroll/
*/
export const ScrollContainer: React.FC<ScrollContainerProps> = ({
children,
scrollKey,
childRef,
shouldUpdateScroll = defaultShouldUpdateScroll,
}) => {
const scrollBehaviorContext = useContext(ScrollBehaviorContext);
const containerRef = useRef<HTMLElement>();
/**
* If a childRef is passed, sync it with the containerRef. This
* is necessary because in this component's return statement,
* we're overwriting the immediate child component's ref prop.
*/
useImperativeHandle(childRef, () => containerRef.current, []);
/**
* Register/unregister scrollable element with ScrollBehavior
*/
useEffect(() => {
if (!scrollBehaviorContext || !containerRef.current) {
return;
}
scrollBehaviorContext.registerElement(
scrollKey,
containerRef.current,
(prevLocation, location) => {
// Hack to allow accessing scrollBehavior._stateStorage
return shouldUpdateScroll.call(
scrollBehaviorContext.scrollBehavior,
prevLocation,
location,
);
},
);
return () => {
scrollBehaviorContext.unregisterElement(scrollKey);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return React.Children.only(
React.cloneElement(children, { ref: containerRef }),
);
};

View File

@@ -0,0 +1,141 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import type { LocationBase } from 'scroll-behavior';
import ScrollBehavior from 'scroll-behavior';
import type {
LocationState,
MastodonLocation,
} from 'mastodon/components/router';
import { usePrevious } from 'mastodon/hooks/usePrevious';
import { defaultShouldUpdateScroll } from './default_should_update_scroll';
import type { ShouldUpdateScrollFn } from './default_should_update_scroll';
import { SessionStorage } from './state_storage';
type ScrollBehaviorInstance = InstanceType<
typeof ScrollBehavior<LocationBase, MastodonLocation>
>;
export interface ScrollBehaviorContextType {
registerElement: (
key: string,
element: HTMLElement,
shouldUpdateScroll: (
prevLocationContext: MastodonLocation | null,
locationContext: MastodonLocation,
) => boolean,
) => void;
unregisterElement: (key: string) => void;
scrollBehavior?: ScrollBehaviorInstance;
}
export const ScrollBehaviorContext =
React.createContext<ScrollBehaviorContextType | null>(null);
interface ScrollContextProps {
shouldUpdateScroll?: ShouldUpdateScrollFn;
children: React.ReactElement;
}
/**
* A top-level wrapper that provides the app with an instance of the
* ScrollBehavior object. scroll-behavior is a library for managing the
* scroll position of a single-page app in the same way the browser would
* normally do for a multi-page app. This means it'll scroll back to top
* when navigating to a new page, and will restore the scroll position
* when navigating e.g. using `history.back`.
* The library keeps a record of scroll positions in session storage.
*
* This component is a port of the unmaintained https://github.com/ytase/react-router-scroll/
*/
export const ScrollContext: React.FC<ScrollContextProps> = ({
children,
shouldUpdateScroll = defaultShouldUpdateScroll,
}) => {
const location = useLocation<LocationState>();
const history = useHistory<LocationState>();
/**
* Keep the current location in a mutable ref so that ScrollBehavior's
* `getCurrentLocation` can access it without having to recreate the
* whole ScrollBehavior object
*/
const currentLocationRef = useRef(location);
useEffect(() => {
currentLocationRef.current = location;
}, [location]);
/**
* Initialise ScrollBehavior object once using state rather
* than a ref to simplify the types and ensure it's defined immediately.
*/
const [scrollBehavior] = useState(
(): ScrollBehaviorInstance =>
new ScrollBehavior({
addNavigationListener: history.listen.bind(history),
stateStorage: new SessionStorage(),
getCurrentLocation: () =>
currentLocationRef.current as unknown as LocationBase,
shouldUpdateScroll: (
prevLocationContext: MastodonLocation | null,
locationContext: MastodonLocation,
) =>
// Hack to allow accessing scrollBehavior._stateStorage
shouldUpdateScroll.call(
scrollBehavior,
prevLocationContext,
locationContext,
),
}),
);
/**
* Handle scroll update when location changes
*/
const prevLocation = usePrevious(location) ?? null;
useEffect(() => {
scrollBehavior.updateScroll(prevLocation, location);
}, [location, prevLocation, scrollBehavior]);
/**
* Stop Scrollbehavior on unmount
*/
useEffect(() => {
return () => {
scrollBehavior.stop();
};
}, [scrollBehavior]);
/**
* Provide the app with a way to register separately scrollable
* elements to also be tracked by ScrollBehavior. (By default
* ScrollBehavior only handles scrolling on the main document body.)
*/
const contextValue = useMemo<ScrollBehaviorContextType>(
() => ({
registerElement: (key, element, shouldUpdateScroll) => {
scrollBehavior.registerElement(
key,
element,
shouldUpdateScroll,
location,
);
},
unregisterElement: (key) => {
scrollBehavior.unregisterElement(key);
},
scrollBehavior,
}),
[location, scrollBehavior],
);
return (
<ScrollBehaviorContext.Provider value={contextValue}>
{React.Children.only(children)}
</ScrollBehaviorContext.Provider>
);
};

View File

@@ -0,0 +1,46 @@
import type { LocationBase, ScrollPosition } from 'scroll-behavior';
const STATE_KEY_PREFIX = '@@scroll|';
interface LocationBaseWithKey extends LocationBase {
key?: string;
}
/**
* This module is part of our port of https://github.com/ytase/react-router-scroll/
* and handles storing scroll positions in SessionStorage.
* Stored positions (`[x, y]`) are keyed by the location key and an optional
* `scrollKey` that's used for to track separately scrollable elements other
* than the document body.
*/
export class SessionStorage {
read(
location: LocationBaseWithKey,
key: string | null,
): ScrollPosition | null {
const stateKey = this.getStateKey(location, key);
try {
const value = sessionStorage.getItem(stateKey);
return value ? (JSON.parse(value) as ScrollPosition) : null;
} catch {
return null;
}
}
save(location: LocationBaseWithKey, key: string | null, value: unknown) {
const stateKey = this.getStateKey(location, key);
const storedValue = JSON.stringify(value);
try {
sessionStorage.setItem(stateKey, storedValue);
} catch {}
}
getStateKey(location: LocationBaseWithKey, key: string | null) {
const locationKey = location.key;
const stateKeyBase = `${STATE_KEY_PREFIX}${locationKey}`;
return key == null ? stateKeyBase : `${stateKeyBase}|${key}`;
}
}

View File

@@ -21,7 +21,7 @@ import { ColumnHeader } from 'mastodon/components/column_header';
import { LoadMore } from 'mastodon/components/load_more';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RadioButton } from 'mastodon/components/radio_button';
import ScrollContainer from 'mastodon/containers/scroll_container';
import { ScrollContainer } from 'mastodon/containers/scroll_container';
import { useSearchParam } from 'mastodon/hooks/useSearchParam';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
@@ -206,7 +206,6 @@ export const Directory: React.FC<{
/>
{multiColumn && !pinned ? (
// @ts-expect-error ScrollContainer is not properly typed yet
<ScrollContainer scrollKey='directory'>
{scrollableArea}
</ScrollContainer>

View File

@@ -16,7 +16,7 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac
import { Hotkeys } from 'mastodon/components/hotkeys';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container';
import { ScrollContainer } from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@@ -526,9 +526,9 @@ class Status extends ImmutablePureComponent {
this.setState({ fullscreen: isFullscreen() });
};
shouldUpdateScroll = (prevRouterProps, { location }) => {
shouldUpdateScroll = (prevLocation, location) => {
// Do not change scroll when opening a modal
if (location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey) {
if (location.state?.mastodonModalKey !== prevLocation?.state?.mastodonModalKey) {
return false;
}
@@ -602,7 +602,7 @@ class Status extends ImmutablePureComponent {
)}
/>
<ScrollContainer scrollKey='thread' shouldUpdateScroll={this.shouldUpdateScroll}>
<ScrollContainer scrollKey='thread' shouldUpdateScroll={this.shouldUpdateScroll} childRef={this.setContainerRef}>
<div className={classNames('scrollable item-list', { fullscreen })} ref={this.setContainerRef}>
{ancestors}

View File

@@ -60,23 +60,13 @@ export function focusColumn({
* Get the index of the currently focused item in one of our item lists
*/
export function getFocusedItemIndex() {
const focusedElement = document.activeElement;
const itemList = focusedElement?.closest('.item-list');
if (!focusedElement || !itemList) {
return -1;
}
let focusedItem: HTMLElement | null = null;
if (focusedElement.parentElement === itemList) {
focusedItem = focusedElement as HTMLElement;
} else {
focusedItem = focusedElement.closest('.item-list > *');
}
const focusedItem = document.activeElement?.closest('.item-list > *');
if (!focusedItem) return -1;
const items = Array.from(itemList.children);
const { parentElement } = focusedItem;
if (!parentElement) return -1;
const items = Array.from(parentElement.children);
return items.indexOf(focusedItem);
}

View File

@@ -864,6 +864,14 @@
"status.cancel_reblog_private": "Прыбраць",
"status.cannot_quote": "Вы не маеце дазвол цытаваць гэты допіс",
"status.cannot_reblog": "Гэты допіс нельга пашырыць",
"status.contains_quote": "Утрымлівае цытату",
"status.context.loading": "Загружаюцца іншыя адказы",
"status.context.loading_error": "Немагчыма загрузіць новыя адказы",
"status.context.loading_more": "Загружаюцца іншыя адказы",
"status.context.loading_success": "Усе адказы загружаныя",
"status.context.more_replies_found": "Знойдзеныя іншыя адказы",
"status.context.retry": "Паспрабаваць зноў",
"status.context.show": "Паказаць",
"status.continued_thread": "Працяг ланцужка",
"status.copy": "Скапіраваць спасылку на допіс",
"status.delete": "Выдаліць",
@@ -901,6 +909,7 @@
"status.quote_error.revoked": "Аўтар выдаліў допіс",
"status.quote_followers_only": "Толькі падпісчыкі могуць цытаваць гэты допіс",
"status.quote_manual_review": "Аўтар зробіць агляд уручную",
"status.quote_noun": "Цытаваць",
"status.quote_policy_change": "Змяніць, хто можа цытаваць",
"status.quote_post_author": "Цытаваў допіс @{name}",
"status.quote_private": "Прыватныя допісы нельга цытаваць",

View File

@@ -865,6 +865,13 @@
"status.cannot_quote": "Nemáte oprávnění citovat tento příspěvek",
"status.cannot_reblog": "Tento příspěvek nemůže být boostnutý",
"status.contains_quote": "Obsahuje citaci",
"status.context.loading": "Načítání dalších odpovědí",
"status.context.loading_error": "Nelze načíst nové odpovědi",
"status.context.loading_more": "Načítání dalších odpovědí",
"status.context.loading_success": "Všechny odpovědi načteny",
"status.context.more_replies_found": "Nalezeny další odpovědi",
"status.context.retry": "Zkusit znovu",
"status.context.show": "Zobrazit",
"status.continued_thread": "Pokračuje ve vlákně",
"status.copy": "Zkopírovat odkaz na příspěvek",
"status.delete": "Smazat",

View File

@@ -865,12 +865,12 @@
"status.cannot_quote": "Dir ist es nicht gestattet, diesen Beitrag zu zitieren",
"status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
"status.contains_quote": "Enthält Zitat",
"status.context.loading": "Weitere Antworten werden geladen",
"status.context.loading_error": "Neue Antworten konnten nicht geladen werden",
"status.context.loading_more": "Weitere Antworten werden geladen",
"status.context.loading_success": "Alle Antworten geladen",
"status.context.loading": "Weitere Antworten laden",
"status.context.loading_error": "Weitere Antworten konnten nicht geladen werden",
"status.context.loading_more": "Weitere Antworten laden",
"status.context.loading_success": "Alle weiteren Antworten geladen",
"status.context.more_replies_found": "Weitere Antworten verfügbar",
"status.context.retry": "Wiederholen",
"status.context.retry": "Erneut versuchen",
"status.context.show": "Anzeigen",
"status.continued_thread": "Fortgeführter Thread",
"status.copy": "Link zum Beitrag kopieren",

View File

@@ -865,6 +865,13 @@
"status.cannot_quote": "Sul pole õigust seda postitust tsiteerida",
"status.cannot_reblog": "Seda postitust ei saa jagada",
"status.contains_quote": "Sisaldab tsitaati",
"status.context.loading": "Laadin veel vastuseid",
"status.context.loading_error": "Uute vastuste laadimine ei õnnestunud",
"status.context.loading_more": "Laadin veel vastuseid",
"status.context.loading_success": "Kõik vastused on laaditud",
"status.context.more_replies_found": "Leidub veel vastuseid",
"status.context.retry": "Proovi uuesti",
"status.context.show": "Näita",
"status.continued_thread": "Jätkatud lõim",
"status.copy": "Kopeeri postituse link",
"status.delete": "Kustuta",

View File

@@ -865,6 +865,13 @@
"status.cannot_quote": "Tú hevur ikki loyvi at sitera hendan postin",
"status.cannot_reblog": "Tað ber ikki til at stimbra hendan postin",
"status.contains_quote": "Inniheldur sitat",
"status.context.loading": "Tekur fleiri svar niður",
"status.context.loading_error": "Fekk ikki tikið nýggj svar niður",
"status.context.loading_more": "Tekur fleiri svar niður",
"status.context.loading_success": "Øll svar tikin niður",
"status.context.more_replies_found": "Fleiri svar funnin",
"status.context.retry": "Royn aftur",
"status.context.show": "Vís",
"status.continued_thread": "Framhaldandi tráður",
"status.copy": "Kopiera leinki til postin",
"status.delete": "Strika",

View File

@@ -865,6 +865,13 @@
"status.cannot_quote": "Ní cheadaítear duit an post seo a lua",
"status.cannot_reblog": "Ní féidir an phostáil seo a mholadh",
"status.contains_quote": "Tá luachan ann",
"status.context.loading": "Ag lódáil tuilleadh freagraí",
"status.context.loading_error": "Níorbh fhéidir freagraí nua a lódáil",
"status.context.loading_more": "Ag lódáil tuilleadh freagraí",
"status.context.loading_success": "Luchtaithe na freagraí uile",
"status.context.more_replies_found": "Tuilleadh freagraí aimsithe",
"status.context.retry": "Déan iarracht arís",
"status.context.show": "Taispeáin",
"status.continued_thread": "Snáithe ar lean",
"status.copy": "Cóipeáil an nasc chuig an bpostáil",
"status.delete": "Scrios",

View File

@@ -865,6 +865,13 @@
"status.cannot_quote": "Je bent niet gemachtigd om dit bericht te citeren",
"status.cannot_reblog": "Dit bericht kan niet geboost worden",
"status.contains_quote": "Bevat citaat",
"status.context.loading": "Meer reacties laden",
"status.context.loading_error": "Kon geen nieuwe reacties laden",
"status.context.loading_more": "Meer reacties laden",
"status.context.loading_success": "Alle reacties zijn geladen",
"status.context.more_replies_found": "Meer reacties gevonden",
"status.context.retry": "Opnieuw proberen",
"status.context.show": "Tonen",
"status.continued_thread": "Vervolg van gesprek",
"status.copy": "Link naar bericht kopiëren",
"status.delete": "Verwijderen",

View File

@@ -2972,9 +2972,13 @@ a.account__display-name {
justify-content: flex-start;
position: relative;
.layout-multiple-columns & {
overflow-x: auto;
&.unscrollable {
overflow-x: hidden;
}
}
&__panels {
box-sizing: border-box;

View File

@@ -97,15 +97,17 @@ class Form::AdminSettings
}.freeze
DESCRIPTION_LIMIT = 200
DOMAIN_BLOCK_AUDIENCES = %w(disabled users all).freeze
REGISTRATION_MODES = %w(open approved none).freeze
attr_accessor(*KEYS)
validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) }
validates :registrations_mode, inclusion: { in: REGISTRATION_MODES }, if: -> { defined?(@registrations_mode) }
validates :site_contact_email, :site_contact_username, presence: true, if: -> { defined?(@site_contact_username) || defined?(@site_contact_email) }
validates :site_contact_username, existing_username: true, if: -> { defined?(@site_contact_username) }
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) }
validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) }
validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) }
validates :show_domain_blocks, inclusion: { in: DOMAIN_BLOCK_AUDIENCES }, if: -> { defined?(@show_domain_blocks) }
validates :show_domain_blocks_rationale, inclusion: { in: DOMAIN_BLOCK_AUDIENCES }, if: -> { defined?(@show_domain_blocks_rationale) }
validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) }
validates :min_age, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@min_age) }
validates :site_short_description, length: { maximum: DESCRIPTION_LIMIT }, if: -> { defined?(@site_short_description) }

View File

@@ -24,7 +24,7 @@
.fields-row__column.fields-row__column-6.fields-group
= f.input :show_domain_blocks,
collection_wrapper_tag: 'ul',
collection: %i(disabled users all),
collection: f.object.class::DOMAIN_BLOCK_AUDIENCES,
include_blank: false,
item_wrapper_tag: 'li',
label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") },
@@ -32,7 +32,7 @@
.fields-row__column.fields-row__column-6.fields-group
= f.input :show_domain_blocks_rationale,
collection_wrapper_tag: 'ul',
collection: %i(disabled users all),
collection: f.object.class::DOMAIN_BLOCK_AUDIENCES,
include_blank: false,
item_wrapper_tag: 'li',
label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") },

View File

@@ -18,7 +18,7 @@
.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :registrations_mode,
collection: %w(open approved none),
collection: f.object.class::REGISTRATION_MODES,
include_blank: false,
label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") },
warning_hint: I18n.t('admin.settings.registrations_mode.warning_hint'),

View File

@@ -101,7 +101,6 @@
"react-redux-loading-bar": "^5.0.8",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
"react-router-scroll-4": "^1.0.0-beta.1",
"react-select": "^5.7.3",
"react-sparklines": "^1.7.0",
"react-swipeable-views": "^0.14.0",
@@ -113,6 +112,7 @@
"rollup-plugin-gzip": "^4.1.1",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "^1.62.1",
"scroll-behavior": "^0.11.0",
"stacktrace-js": "^2.0.2",
"stringz": "^2.1.0",
"substring-trie": "^1.0.2",

View File

@@ -2838,7 +2838,6 @@ __metadata:
react-redux-loading-bar: "npm:^5.0.8"
react-router: "npm:^5.3.4"
react-router-dom: "npm:^5.3.4"
react-router-scroll-4: "npm:^1.0.0-beta.1"
react-select: "npm:^5.7.3"
react-sparklines: "npm:^1.7.0"
react-swipeable-views: "npm:^0.14.0"
@@ -2851,6 +2850,7 @@ __metadata:
rollup-plugin-gzip: "npm:^4.1.1"
rollup-plugin-visualizer: "npm:^6.0.3"
sass: "npm:^1.62.1"
scroll-behavior: "npm:^0.11.0"
stacktrace-js: "npm:^2.0.2"
storybook: "npm:^9.1.1"
stringz: "npm:^2.1.0"
@@ -6487,16 +6487,7 @@ __metadata:
languageName: node
linkType: hard
"dom-helpers@npm:^3.4.0":
version: 3.4.0
resolution: "dom-helpers@npm:3.4.0"
dependencies:
"@babel/runtime": "npm:^7.1.2"
checksum: 10c0/1d2d3e4eadac2c4f4c8c7470a737ab32b7ec28237c4d094ea967ec3184168fd12452196fcc424a5d7860b6176117301aeaecba39467bf1a6e8492a8e5c9639d1
languageName: node
linkType: hard
"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.2.0":
"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.1.4, dom-helpers@npm:^5.2.0":
version: 5.2.1
resolution: "dom-helpers@npm:5.2.1"
dependencies:
@@ -10052,6 +10043,13 @@ __metadata:
languageName: node
linkType: hard
"page-lifecycle@npm:^0.1.2":
version: 0.1.2
resolution: "page-lifecycle@npm:0.1.2"
checksum: 10c0/509dbbc2ad2000dffcf591f66ab13d80fb1dba9337d85c76269173f7a5c3959b5a876e3bfb1e4494f6b932c1dc02a0b5824ebd452ab1a7204d4abdf498cb27c5
languageName: node
linkType: hard
"parent-module@npm:^1.0.0":
version: 1.0.1
resolution: "parent-module@npm:1.0.1"
@@ -11293,21 +11291,6 @@ __metadata:
languageName: node
linkType: hard
"react-router-scroll-4@npm:^1.0.0-beta.1":
version: 1.0.0-beta.2
resolution: "react-router-scroll-4@npm:1.0.0-beta.2"
dependencies:
scroll-behavior: "npm:^0.9.1"
warning: "npm:^3.0.0"
peerDependencies:
prop-types: ^15.6.0
react: ^15.0.0 || ^16.0.0
react-dom: ^15.0.0 || ^16.0.0
react-router-dom: ^4.0
checksum: 10c0/ad195b7359fd3146530cf299ec437f0a619c577b2cacfb2c76a156d3cd9d5d3e97af56e17c300c37ca8c485041e93124fe63f0c86db6aea468caf838281e62cb
languageName: node
linkType: hard
"react-router@npm:5.3.4, react-router@npm:^5.3.4":
version: 5.3.4
resolution: "react-router@npm:5.3.4"
@@ -12067,13 +12050,14 @@ __metadata:
languageName: node
linkType: hard
"scroll-behavior@npm:^0.9.1":
version: 0.9.12
resolution: "scroll-behavior@npm:0.9.12"
"scroll-behavior@npm:^0.11.0":
version: 0.11.0
resolution: "scroll-behavior@npm:0.11.0"
dependencies:
dom-helpers: "npm:^3.4.0"
dom-helpers: "npm:^5.1.4"
invariant: "npm:^2.2.4"
checksum: 10c0/4f438c48b93a1dcc2ab51a18670fac6f5ce41885291d8aa13251b4a187be9d0c6dd518ee974eb52ac9bbe227b9811c2615ecca73192a1a190b78dfdadb9c2cf2
page-lifecycle: "npm:^0.1.2"
checksum: 10c0/c54010c9fdd9fc360fd7887ecf64f16972f9557ac679723709612cd54fc4778c7433ab46a9637933179ef31471f78e2591fb35351dc0e15537fecf1c8c89d32c
languageName: node
linkType: hard
@@ -14029,15 +14013,6 @@ __metadata:
languageName: node
linkType: hard
"warning@npm:^3.0.0":
version: 3.0.0
resolution: "warning@npm:3.0.0"
dependencies:
loose-envify: "npm:^1.0.0"
checksum: 10c0/6a2a56ab3139d3927193d926a027e74e1449fa47cc692feea95f8a81a4bb5b7f10c312def94cce03f3b58cb26ba3247858e75d17d596451d2c483a62e8204705
languageName: node
linkType: hard
"warning@npm:^4.0.1, warning@npm:^4.0.3":
version: 4.0.3
resolution: "warning@npm:4.0.3"