Merge remote-tracking branch 'origin/main' into ditto-auth

environments/review-ditto-auth-csh1vn/deployments/4454
Alex Gleason 7 months ago
commit 603e2cc2ce
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7

@ -20,7 +20,7 @@ interface IAnimatedNumber {
}
const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {
const reduceMotion = useSettings().get('reduceMotion');
const { reduceMotion } = useSettings();
const [direction, setDirection] = useState(1);
const [displayedValue, setDisplayedValue] = useState<number>(value);

@ -13,7 +13,7 @@ interface IEmoji {
}
const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
const autoPlayGif = useSettings().get('autoPlayGif');
const { autoPlayGif } = useSettings();
// @ts-ignore
if (unicodeMapping[emoji]) {

@ -20,7 +20,7 @@ interface IReactionsBar {
}
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addReaction, removeReaction, emojiMap }) => {
const reduceMotion = useSettings().get('reduceMotion');
const { reduceMotion } = useSettings();
const handleEmojiPick = (data: Emoji) => {
addReaction(announcementId, (data as NativeEmoji).native.replace(/:/g, ''));

@ -23,7 +23,7 @@ const Helmet: React.FC<IHelmet> = ({ children }) => {
const instance = useInstance();
const { unreadChatsCount } = useStatContext();
const unreadCount = useAppSelector((state) => getNotifTotals(state) + unreadChatsCount);
const demetricator = useSettings().get('demetricator');
const { demetricator } = useSettings();
const hasUnreadNotifications = React.useMemo(() => !(unreadCount < 1 || demetricator), [unreadCount, demetricator]);

@ -72,8 +72,7 @@ const Item: React.FC<IItem> = ({
last,
total,
}) => {
const settings = useSettings();
const autoPlayGif = settings.get('autoPlayGif') === true;
const { autoPlayGif } = useSettings();
const { mediaPreview } = useSoapboxConfig();
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => {

@ -9,9 +9,8 @@ interface INavlinks {
}
const Navlinks: React.FC<INavlinks> = ({ type }) => {
const settings = useSettings();
const { locale } = useSettings();
const { copyright, navlinks } = useSoapboxConfig();
const locale = settings.get('locale') as string;
return (
<footer className='relative mx-auto mt-auto max-w-7xl py-8'>

@ -35,8 +35,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
const intl = useIntl();
const history = useHistory();
const settings = useSettings();
const displayMedia = settings.get('displayMedia');
const { displayMedia } = useSettings();
const overlay = useRef<HTMLDivElement>(null);

@ -27,14 +27,13 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
autoloadThreshold = 50,
}) => {
const intl = useIntl();
const settings = useSettings();
const { autoloadTimelines } = useSettings();
// Whether we are scrolled past the `threshold`.
const [scrolled, setScrolled] = useState<boolean>(false);
// Whether we are scrolled above the `autoloadThreshold`.
const [scrolledTop, setScrolledTop] = useState<boolean>(false);
const autoload = settings.get('autoloadTimelines') === true;
const visible = count > 0 && scrolled;
/** Number of pixels scrolled down from the top of the page. */
@ -44,10 +43,10 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
/** Unload feed items if scrolled to the top. */
const maybeUnload = useCallback(() => {
if (autoload && scrolledTop && count) {
if (autoloadTimelines && scrolledTop && count) {
onClick();
}
}, [autoload, scrolledTop, count, onClick]);
}, [autoloadTimelines, scrolledTop, count, onClick]);
/** Set state while scrolling. */
const handleScroll = useCallback(throttle(() => {

@ -106,8 +106,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
useWindowScroll = true,
}, ref) => {
const history = useHistory();
const settings = useSettings();
const autoloadMore = settings.get('autoloadMore');
const { autoloadMore } = useSettings();
// Preserve scroll position
const scrollDataKey = `soapbox:scrollData:${scrollKey}`;

@ -24,7 +24,7 @@ const SidebarNavigation = () => {
const instance = useInstance();
const features = useFeatures();
const settings = useSettings();
const { isDeveloper } = useSettings();
const { account } = useOwnAccount();
const groupsPath = useGroupsPath();
@ -71,7 +71,7 @@ const SidebarNavigation = () => {
});
}
if (settings.get('isDeveloper')) {
if (isDeveloper) {
menu.push({
to: '/developers',
icon: require('@tabler/icons/code.svg'),

@ -13,7 +13,7 @@ interface ISiteLogo extends React.ComponentProps<'img'> {
/** Display the most appropriate site logo based on the theme and configuration. */
const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
const { logo, logoDarkMode } = useSoapboxConfig();
const settings = useSettings();
const { demo } = useSettings();
let darkMode = useTheme() === 'dark';
if (theme === 'dark') darkMode = true;
@ -26,7 +26,7 @@ const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
// Use the right logo if provided, then use fallbacks.
const getSrc = () => {
// In demo mode, use the Soapbox logo.
if (settings.get('demo')) return soapboxLogo;
if (demo) return soapboxLogo;
return (darkMode && logoDarkMode)
? logoDarkMode

@ -136,7 +136,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const me = useAppSelector(state => state.me);
const { groupRelationship } = useGroupRelationship(status.group?.id);
const features = useFeatures();
const settings = useSettings();
const { boostModal, deleteModal } = useSettings();
const soapboxConfig = useSoapboxConfig();
const { allowedEmoji } = soapboxConfig;
@ -208,7 +208,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const handleReblogClick: React.EventHandler<React.MouseEvent> = e => {
if (me) {
const modalReblog = () => dispatch(toggleReblog(status));
const boostModal = settings.get('boostModal');
if ((e && e.shiftKey) || !boostModal) {
modalReblog();
} else {
@ -229,7 +228,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const doDeleteStatus = (withRedraft = false) => {
dispatch((_, getState) => {
const deleteModal = settings.get('deleteModal');
if (!deleteModal) {
dispatch(deleteStatus(status.id, withRedraft));
} else {

@ -75,8 +75,7 @@ const Status: React.FC<IStatus> = (props) => {
const history = useHistory();
const dispatch = useAppDispatch();
const settings = useSettings();
const displayMedia = settings.get('displayMedia') as string;
const { displayMedia, boostModal } = useSettings();
const didShowCard = useRef(false);
const node = useRef<HTMLDivElement>(null);
const overlay = useRef<HTMLDivElement>(null);
@ -155,7 +154,6 @@ const Status: React.FC<IStatus> = (props) => {
const handleHotkeyBoost = (e?: KeyboardEvent): void => {
const modalReblog = () => dispatch(toggleReblog(actualStatus));
const boostModal = settings.get('boostModal');
if ((e && e.shiftKey) || !boostModal) {
modalReblog();
} else {

@ -38,12 +38,11 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
const { account } = useOwnAccount();
const dispatch = useAppDispatch();
const intl = useIntl();
const settings = useSettings();
const { displayMedia, deleteModal } = useSettings();
const { links } = useSoapboxConfig();
const isUnderReview = status.visibility === 'self';
const isOwnStatus = status.getIn(['account', 'id']) === account?.id;
const displayMedia = settings.get('displayMedia') as string;
const [visible, setVisible] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
@ -58,7 +57,6 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
};
const handleDeleteStatus = () => {
const deleteModal = settings.get('deleteModal');
if (!deleteModal) {
dispatch(deleteStatus(status.id, false));
} else {

@ -34,7 +34,7 @@ const StatusInfo = (props: IStatusInfo) => {
{icon}
</div>
<Text size='xs' theme='muted' weight='medium'>
<Text size='xs' theme='muted' weight='medium' truncate>
{text}
</Text>
</HStack>

@ -22,8 +22,7 @@ export interface IStillImage {
/** Renders images on a canvas, only playing GIFs if autoPlayGif is enabled. */
const StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterboxed = false, showExt = false, onError }) => {
const settings = useSettings();
const autoPlayGif = settings.get('autoPlayGif');
const { autoPlayGif } = useSettings();
const canvas = useRef<HTMLCanvasElement>(null);
const img = useRef<HTMLImageElement>(null);

@ -5,8 +5,6 @@ import { toggleMainWindow } from 'soapbox/actions/chats';
import { useAppDispatch, useOwnAccount, useSettings } from 'soapbox/hooks';
import { IChat, useChat } from 'soapbox/queries/chats';
type WindowState = 'open' | 'minimized';
const ChatContext = createContext<any>({
isOpen: false,
needsAcceptance: false,
@ -26,7 +24,7 @@ interface IChatProvider {
const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
const history = useHistory();
const dispatch = useAppDispatch();
const settings = useSettings();
const { chats } = useSettings();
const { account } = useOwnAccount();
const path = history.location.pathname;
@ -38,9 +36,8 @@ const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
const { data: chat } = useChat(currentChatId as string);
const mainWindowState = settings.getIn(['chats', 'mainWindow']) as WindowState;
const needsAcceptance = !chat?.accepted && chat?.created_by_account !== account?.id;
const isOpen = mainWindowState === 'open';
const isOpen = chats.mainWindow === 'open';
const changeScreen = (screen: ChatWidgetScreens, currentChatId?: string | null) => {
setCurrentChatId(currentChatId || null);

@ -18,7 +18,7 @@ const AboutPage: React.FC = () => {
const soapboxConfig = useSoapboxConfig();
const [pageHtml, setPageHtml] = useState<string>('');
const [locale, setLocale] = useState<string>(settings.get('locale'));
const [locale, setLocale] = useState<string>(settings.locale);
const { aboutPages } = soapboxConfig;

@ -15,10 +15,7 @@ interface IMediaItem {
}
const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
const settings = useSettings();
const autoPlayGif = settings.get('autoPlayGif');
const displayMedia = settings.get('displayMedia');
const { autoPlayGif, displayMedia } = useSettings();
const [visible, setVisible] = useState<boolean>(displayMedia !== 'hide_all' && !attachment.status?.sensitive || displayMedia === 'show_all');
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = e => {

@ -32,7 +32,7 @@ const AccountTimeline: React.FC<IAccountTimeline> = ({ params, withReplies = fal
const [accountLoading, setAccountLoading] = useState<boolean>(!account);
const path = withReplies ? `${account?.id}:with_replies` : account?.id;
const showPins = settings.getIn(['account_timeline', 'shows', 'pinned']) === true && !withReplies;
const showPins = settings.account_timeline.shows.pinned && !withReplies;
const statusIds = useAppSelector(state => getStatusIds(state, { type: `account:${path}`, prefix: 'account_timeline' }));
const featuredStatusIds = useAppSelector(state => getStatusIds(state, { type: `account:${account?.id}:pinned`, prefix: 'account_timeline' }));

@ -41,11 +41,10 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const history = useHistory();
const dispatch = useAppDispatch();
const settings = useSettings();
const { locale } = useSettings();
const features = useFeatures();
const instance = useInstance();
const locale = settings.get('locale');
const needsConfirmation = instance.pleroma.metadata.account_activation_required;
const needsApproval = instance.registrations.approval_required;
const supportsEmailList = features.emailList;

@ -18,7 +18,7 @@ const CommunityTimeline = () => {
const dispatch = useAppDispatch();
const settings = useSettings();
const onlyMedia = !!settings.getIn(['community', 'other', 'onlyMedia'], false);
const onlyMedia = settings.community.other.onlyMedia;
const next = useAppSelector(state => state.timelines.get('community')?.next);
const timelineId = 'community';

@ -71,7 +71,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
const history = useHistory();
const features = useFeatures();
const settings = useSettings();
const { boostModal } = useSettings();
const { account: ownAccount } = useOwnAccount();
const isStaff = ownAccount ? ownAccount.staff : false;
const isAdmin = ownAccount ? ownAccount.admin : false;
@ -123,7 +123,6 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
const handleReblogClick = () => {
const modalReblog = () => dispatch(toggleReblog(status));
const boostModal = settings.get('boostModal');
if (!boostModal) {
modalReblog();
} else {

@ -28,8 +28,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity;
const { tileServer } = useSoapboxConfig();
const settings = useSettings();
const displayMedia = settings.get('displayMedia') as string;
const { displayMedia } = useSettings();
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));

@ -25,8 +25,8 @@ const NotificationFilterBar = () => {
const settings = useSettings();
const features = useFeatures();
const selectedFilter = settings.getIn(['notifications', 'quickFilter', 'active']) as string;
const advancedMode = settings.getIn(['notifications', 'quickFilter', 'advanced']);
const selectedFilter = settings.notifications.quickFilter.active;
const advancedMode = settings.notifications.quickFilter.advanced;
const onClick = (notificationType: string) => () => dispatch(setFilter(notificationType));

@ -1,14 +1,14 @@
import get from 'lodash/get';
import React from 'react';
import { Toggle } from 'soapbox/components/ui';
import type { Map as ImmutableMap } from 'immutable';
import { Settings } from 'soapbox/schemas/soapbox/settings';
interface ISettingToggle {
/** Unique identifier for the Toggle. */
id?: string;
/** The full user settings map. */
settings: ImmutableMap<string, any>;
settings: Settings;
/** Array of key names leading into the setting map. */
settingPath: string[];
/** Callback when the setting is toggled. */
@ -25,7 +25,7 @@ const SettingToggle: React.FC<ISettingToggle> = ({ id, settings, settingPath, on
return (
<Toggle
id={id}
checked={!!settings.getIn(settingPath)}
checked={!!get(settings, settingPath)}
onChange={handleChange}
/>
);

@ -50,8 +50,8 @@ const Notifications = () => {
const intl = useIntl();
const settings = useSettings();
const showFilterBar = settings.getIn(['notifications', 'quickFilter', 'show']);
const activeFilter = settings.getIn(['notifications', 'quickFilter', 'active']);
const showFilterBar = settings.notifications.quickFilter.show;
const activeFilter = settings.notifications.quickFilter.active;
const notifications = useAppSelector(state => getNotifications(state));
const isLoading = useAppSelector(state => state.notifications.isLoading);
// const isUnread = useAppSelector(state => state.notifications.unread > 0);

@ -139,7 +139,7 @@ const Preferences = () => {
<SelectDropdown
className='max-w-[200px]'
items={languages}
defaultValue={settings.get('locale') as string | undefined}
defaultValue={settings.locale}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['locale'])}
/>
</ListItem>
@ -148,7 +148,7 @@ const Preferences = () => {
<SelectDropdown
className='max-w-[200px]'
items={displayMediaOptions}
defaultValue={settings.get('displayMedia') as string | undefined}
defaultValue={settings.displayMedia}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['displayMedia'])}
/>
</ListItem>
@ -158,7 +158,7 @@ const Preferences = () => {
<SelectDropdown
className='max-w-[200px]'
items={defaultPrivacyOptions}
defaultValue={settings.get('defaultPrivacy') as string | undefined}
defaultValue={settings.defaultPrivacy}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultPrivacy'])}
/>
</ListItem>
@ -169,7 +169,7 @@ const Preferences = () => {
<SelectDropdown
className='max-w-[200px]'
items={defaultContentTypeOptions}
defaultValue={settings.get('defaultContentType') as string | undefined}
defaultValue={settings.defaultContentType}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultContentType'])}
/>
</ListItem>

@ -23,13 +23,13 @@ const CommunityTimeline = () => {
const instance = useInstance();
const settings = useSettings();
const onlyMedia = !!settings.getIn(['public', 'other', 'onlyMedia'], false);
const onlyMedia = settings.public.other.onlyMedia;
const next = useAppSelector(state => state.timelines.get('public')?.next);
const timelineId = 'public';
const explanationBoxExpanded = settings.get('explanationBox');
const showExplanationBox = settings.get('showExplanationBox');
const explanationBoxExpanded = settings.explanationBox;
const showExplanationBox = settings.showExplanationBox;
const dismissExplanationBox = () => {
dispatch(changeSetting(['showExplanationBox'], false));

@ -10,9 +10,9 @@ interface IPinnedHostsPicker {
const PinnedHostsPicker: React.FC<IPinnedHostsPicker> = ({ host: activeHost }) => {
const settings = useSettings();
const pinnedHosts = settings.getIn(['remote_timeline', 'pinnedHosts']) as any;
const pinnedHosts = settings.remote_timeline.pinnedHosts;
if (!pinnedHosts || pinnedHosts.isEmpty()) return null;
if (!pinnedHosts.length) return null;
return (
<HStack className='mb-4' space={2}>

@ -27,10 +27,10 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
const settings = useSettings();
const timelineId = 'remote';
const onlyMedia = !!settings.getIn(['remote', 'other', 'onlyMedia']);
const onlyMedia = settings.remote.other.onlyMedia;
const next = useAppSelector(state => state.timelines.get('remote')?.next);
const pinned: boolean = (settings.getIn(['remote_timeline', 'pinnedHosts']) as any).includes(instance);
const pinned = settings.remote_timeline.pinnedHosts.includes(instance);
const handleCloseClick: React.MouseEventHandler = () => {
history.push('/timeline/fediverse');

@ -26,8 +26,6 @@ import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/statu
import DetailedStatus from './detailed-status';
import ThreadStatus from './thread-status';
type DisplayMedia = 'default' | 'hide_all' | 'show_all';
const getAncestorsIds = createSelector([
(_: RootState, statusId: string | undefined) => statusId,
(state: RootState) => state.contexts.inReplyTos,
@ -96,9 +94,8 @@ const Thread = (props: IThread) => {
const dispatch = useAppDispatch();
const history = useHistory();
const intl = useIntl();
const settings = useSettings();
const { displayMedia } = useSettings();
const displayMedia = settings.get('displayMedia') as DisplayMedia;
const isUnderReview = status?.visibility === 'self';
const { ancestorsIds, descendantsIds } = useAppSelector((state) => {

@ -25,7 +25,7 @@ const InstanceInfoPanel: React.FC<IInstanceInfoPanel> = ({ host }) => {
const settings = useSettings();
const remoteInstance: any = useAppSelector(state => getRemoteInstance(state, host));
const pinned: boolean = (settings.getIn(['remote_timeline', 'pinnedHosts']) as any).includes(host);
const pinned = settings.remote_timeline.pinnedHosts.includes(host);
const handlePinHost = () => {
if (!pinned) {

@ -7,10 +7,9 @@ import { useInstance, useSettings, useSoapboxConfig } from 'soapbox/hooks';
const PromoPanel: React.FC = () => {
const instance = useInstance();
const { promoPanel } = useSoapboxConfig();
const settings = useSettings();
const { locale } = useSettings();
const promoItems = promoPanel.get('items');
const locale = settings.get('locale');
if (!promoItems || promoItems.isEmpty()) return null;

@ -8,7 +8,7 @@ import ThemeSelector from './theme-selector';
/** Stateful theme selector. */
const ThemeToggle: React.FC = () => {
const dispatch = useAppDispatch();
const themeMode = useSettings().get('themeMode');
const { themeMode } = useSettings();
const handleChange = (themeMode: string) => {
dispatch(changeSetting(['themeMode'], themeMode));

@ -6,7 +6,7 @@ import { useSettings } from 'soapbox/hooks';
import ReducedMotion from './reduced-motion';
const OptionalMotion = (props: MotionProps) => {
const reduceMotion = useSettings().get('reduceMotion');
const { reduceMotion } = useSettings();
return (
reduceMotion ? <ReducedMotion {...props} /> : <Motion {...props} />

@ -43,7 +43,7 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
const history = useHistory();
const { account } = useOwnAccount();
const settings = useSettings();
const { isDeveloper } = useSettings();
const renderComponent = ({ match }: RouteComponentProps) => {
if (Page) {
@ -81,7 +81,7 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
const authorized = [
account || publicRoute,
developerOnly ? settings.get('isDeveloper') : true,
developerOnly ? isDeveloper : true,
staffOnly ? account && account.staff : true,
adminOnly ? account && account.admin : true,
].every(c => c);

@ -1,10 +1,12 @@
import { useMemo } from 'react';
import { getSettings } from 'soapbox/actions/settings';
import { settingsSchema } from 'soapbox/schemas/soapbox/settings';
import { useAppSelector } from './useAppSelector';
import type { Map as ImmutableMap } from 'immutable';
/** Get the user settings from the store */
export const useSettings = (): ImmutableMap<string, any> => {
return useAppSelector((state) => getSettings(state));
export const useSettings = () => {
const data = useAppSelector((state) => getSettings(state));
return useMemo(() => settingsSchema.parse(data.toJS()), [data]);
};

@ -8,12 +8,10 @@ type Theme = 'light' | 'dark';
* regardless of whether that's by system theme or direct setting.
*/
const useTheme = (): Theme => {
const settings = useSettings();
const { themeMode } = useSettings();
const systemTheme = useSystemTheme();
const userTheme = settings.get('themeMode');
const darkMode = userTheme === 'dark' || (userTheme === 'system' && systemTheme === 'dark');
const darkMode = themeMode === 'dark' || (themeMode === 'system' && systemTheme === 'dark');
return darkMode ? 'dark' : 'light';
};

@ -20,18 +20,17 @@ interface ISoapboxHead {
/** Injects metadata into site head with Helmet. */
const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
const { locale, direction } = useLocale();
const settings = useSettings();
const { demo, reduceMotion, underlineLinks, demetricator } = useSettings();
const soapboxConfig = useSoapboxConfig();
const demo = !!settings.get('demo');
const darkMode = useTheme() === 'dark';
const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig);
const dsn = soapboxConfig.sentryDsn;
const bodyClass = clsx('h-full bg-white text-base dark:bg-gray-800', {
'no-reduce-motion': !settings.get('reduceMotion'),
'underline-links': settings.get('underlineLinks'),
'demetricator': settings.get('demetricator'),
'no-reduce-motion': !reduceMotion,
'underline-links': underlineLinks,
'demetricator': demetricator,
});
useEffect(() => {

@ -2,6 +2,8 @@ import { z } from 'zod';
import { locales } from 'soapbox/messages';
import { coerceObject } from '../utils';
const skinToneSchema = z.union([
z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5), z.literal(6),
]);
@ -14,6 +16,7 @@ const settingsSchema = z.object({
autoPlayGif: z.boolean().catch(true),
displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'),
expandSpoilers: z.boolean().catch(false),
preserveSpoilers: z.boolean().catch(false),
unfollowModal: z.boolean().catch(false),
boostModal: z.boolean().catch(false),
deleteModal: z.boolean().catch(true),
@ -29,6 +32,47 @@ const settingsSchema = z.object({
systemFont: z.boolean().catch(false),
demetricator: z.boolean().catch(false),
isDeveloper: z.boolean().catch(false),
demo: z.boolean().catch(false),
chats: coerceObject({
mainWindow: z.enum(['minimized', 'open']).catch('minimized'),
sound: z.boolean().catch(true),
}),
home: coerceObject({
shows: coerceObject({
reblog: z.boolean().catch(true),
reply: z.boolean().catch(true),
}),
}),
account_timeline: coerceObject({
shows: coerceObject({
pinned: z.boolean().catch(true),
}),
}),
remote_timeline: coerceObject({
pinnedHosts: z.string().array().catch([]),
}),
public: coerceObject({
other: coerceObject({
onlyMedia: z.boolean().catch(false),
}),
}),
community: coerceObject({
other: coerceObject({
onlyMedia: z.boolean().catch(false),
}),
}),
remote: coerceObject({
other: coerceObject({
onlyMedia: z.boolean().catch(false),
}),
}),
notifications: coerceObject({
quickFilter: coerceObject({
active: z.string().catch('all'),
advanced: z.boolean().catch(false),
show: z.boolean().catch(true),
}),
}),
});
type Settings = z.infer<typeof settingsSchema>;

Loading…
Cancel
Save