Merge branch 'refactor-use-settings' into 'main'
Refactor useSettings hook, parse with zod schema See merge request soapbox-pub/soapbox!2940
This commit is contained in:
commit
0f6a4a0744
40 changed files with 110 additions and 92 deletions
|
@ -20,7 +20,7 @@ interface IAnimatedNumber {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {
|
const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {
|
||||||
const reduceMotion = useSettings().get('reduceMotion');
|
const { reduceMotion } = useSettings();
|
||||||
|
|
||||||
const [direction, setDirection] = useState(1);
|
const [direction, setDirection] = useState(1);
|
||||||
const [displayedValue, setDisplayedValue] = useState<number>(value);
|
const [displayedValue, setDisplayedValue] = useState<number>(value);
|
||||||
|
|
|
@ -13,7 +13,7 @@ interface IEmoji {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
|
const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
|
||||||
const autoPlayGif = useSettings().get('autoPlayGif');
|
const { autoPlayGif } = useSettings();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (unicodeMapping[emoji]) {
|
if (unicodeMapping[emoji]) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ interface IReactionsBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addReaction, removeReaction, emojiMap }) => {
|
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addReaction, removeReaction, emojiMap }) => {
|
||||||
const reduceMotion = useSettings().get('reduceMotion');
|
const { reduceMotion } = useSettings();
|
||||||
|
|
||||||
const handleEmojiPick = (data: Emoji) => {
|
const handleEmojiPick = (data: Emoji) => {
|
||||||
addReaction(announcementId, (data as NativeEmoji).native.replace(/:/g, ''));
|
addReaction(announcementId, (data as NativeEmoji).native.replace(/:/g, ''));
|
||||||
|
|
|
@ -23,7 +23,7 @@ const Helmet: React.FC<IHelmet> = ({ children }) => {
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const { unreadChatsCount } = useStatContext();
|
const { unreadChatsCount } = useStatContext();
|
||||||
const unreadCount = useAppSelector((state) => getNotifTotals(state) + unreadChatsCount);
|
const unreadCount = useAppSelector((state) => getNotifTotals(state) + unreadChatsCount);
|
||||||
const demetricator = useSettings().get('demetricator');
|
const { demetricator } = useSettings();
|
||||||
|
|
||||||
const hasUnreadNotifications = React.useMemo(() => !(unreadCount < 1 || demetricator), [unreadCount, demetricator]);
|
const hasUnreadNotifications = React.useMemo(() => !(unreadCount < 1 || demetricator), [unreadCount, demetricator]);
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,7 @@ const Item: React.FC<IItem> = ({
|
||||||
last,
|
last,
|
||||||
total,
|
total,
|
||||||
}) => {
|
}) => {
|
||||||
const settings = useSettings();
|
const { autoPlayGif } = useSettings();
|
||||||
const autoPlayGif = settings.get('autoPlayGif') === true;
|
|
||||||
const { mediaPreview } = useSoapboxConfig();
|
const { mediaPreview } = useSoapboxConfig();
|
||||||
|
|
||||||
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => {
|
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = ({ currentTarget: video }) => {
|
||||||
|
|
|
@ -9,9 +9,8 @@ interface INavlinks {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navlinks: React.FC<INavlinks> = ({ type }) => {
|
const Navlinks: React.FC<INavlinks> = ({ type }) => {
|
||||||
const settings = useSettings();
|
const { locale } = useSettings();
|
||||||
const { copyright, navlinks } = useSoapboxConfig();
|
const { copyright, navlinks } = useSoapboxConfig();
|
||||||
const locale = settings.get('locale') as string;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className='relative mx-auto mt-auto max-w-7xl py-8'>
|
<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 intl = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const settings = useSettings();
|
const { displayMedia } = useSettings();
|
||||||
const displayMedia = settings.get('displayMedia');
|
|
||||||
|
|
||||||
const overlay = useRef<HTMLDivElement>(null);
|
const overlay = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|
|
@ -27,14 +27,13 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
|
||||||
autoloadThreshold = 50,
|
autoloadThreshold = 50,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
const { autoloadTimelines } = useSettings();
|
||||||
|
|
||||||
// Whether we are scrolled past the `threshold`.
|
// Whether we are scrolled past the `threshold`.
|
||||||
const [scrolled, setScrolled] = useState<boolean>(false);
|
const [scrolled, setScrolled] = useState<boolean>(false);
|
||||||
// Whether we are scrolled above the `autoloadThreshold`.
|
// Whether we are scrolled above the `autoloadThreshold`.
|
||||||
const [scrolledTop, setScrolledTop] = useState<boolean>(false);
|
const [scrolledTop, setScrolledTop] = useState<boolean>(false);
|
||||||
|
|
||||||
const autoload = settings.get('autoloadTimelines') === true;
|
|
||||||
const visible = count > 0 && scrolled;
|
const visible = count > 0 && scrolled;
|
||||||
|
|
||||||
/** Number of pixels scrolled down from the top of the page. */
|
/** 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. */
|
/** Unload feed items if scrolled to the top. */
|
||||||
const maybeUnload = useCallback(() => {
|
const maybeUnload = useCallback(() => {
|
||||||
if (autoload && scrolledTop && count) {
|
if (autoloadTimelines && scrolledTop && count) {
|
||||||
onClick();
|
onClick();
|
||||||
}
|
}
|
||||||
}, [autoload, scrolledTop, count, onClick]);
|
}, [autoloadTimelines, scrolledTop, count, onClick]);
|
||||||
|
|
||||||
/** Set state while scrolling. */
|
/** Set state while scrolling. */
|
||||||
const handleScroll = useCallback(throttle(() => {
|
const handleScroll = useCallback(throttle(() => {
|
||||||
|
|
|
@ -106,8 +106,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
||||||
useWindowScroll = true,
|
useWindowScroll = true,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const settings = useSettings();
|
const { autoloadMore } = useSettings();
|
||||||
const autoloadMore = settings.get('autoloadMore');
|
|
||||||
|
|
||||||
// Preserve scroll position
|
// Preserve scroll position
|
||||||
const scrollDataKey = `soapbox:scrollData:${scrollKey}`;
|
const scrollDataKey = `soapbox:scrollData:${scrollKey}`;
|
||||||
|
|
|
@ -24,7 +24,7 @@ const SidebarNavigation = () => {
|
||||||
|
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const settings = useSettings();
|
const { isDeveloper } = useSettings();
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
const groupsPath = useGroupsPath();
|
const groupsPath = useGroupsPath();
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ const SidebarNavigation = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.get('isDeveloper')) {
|
if (isDeveloper) {
|
||||||
menu.push({
|
menu.push({
|
||||||
to: '/developers',
|
to: '/developers',
|
||||||
icon: require('@tabler/icons/code.svg'),
|
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. */
|
/** Display the most appropriate site logo based on the theme and configuration. */
|
||||||
const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
|
const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
|
||||||
const { logo, logoDarkMode } = useSoapboxConfig();
|
const { logo, logoDarkMode } = useSoapboxConfig();
|
||||||
const settings = useSettings();
|
const { demo } = useSettings();
|
||||||
|
|
||||||
let darkMode = useTheme() === 'dark';
|
let darkMode = useTheme() === 'dark';
|
||||||
if (theme === 'dark') darkMode = true;
|
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.
|
// Use the right logo if provided, then use fallbacks.
|
||||||
const getSrc = () => {
|
const getSrc = () => {
|
||||||
// In demo mode, use the Soapbox logo.
|
// In demo mode, use the Soapbox logo.
|
||||||
if (settings.get('demo')) return soapboxLogo;
|
if (demo) return soapboxLogo;
|
||||||
|
|
||||||
return (darkMode && logoDarkMode)
|
return (darkMode && logoDarkMode)
|
||||||
? logoDarkMode
|
? logoDarkMode
|
||||||
|
|
|
@ -136,7 +136,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
const me = useAppSelector(state => state.me);
|
const me = useAppSelector(state => state.me);
|
||||||
const { groupRelationship } = useGroupRelationship(status.group?.id);
|
const { groupRelationship } = useGroupRelationship(status.group?.id);
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const settings = useSettings();
|
const { boostModal, deleteModal } = useSettings();
|
||||||
const soapboxConfig = useSoapboxConfig();
|
const soapboxConfig = useSoapboxConfig();
|
||||||
|
|
||||||
const { allowedEmoji } = soapboxConfig;
|
const { allowedEmoji } = soapboxConfig;
|
||||||
|
@ -208,7 +208,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
const handleReblogClick: React.EventHandler<React.MouseEvent> = e => {
|
const handleReblogClick: React.EventHandler<React.MouseEvent> = e => {
|
||||||
if (me) {
|
if (me) {
|
||||||
const modalReblog = () => dispatch(toggleReblog(status));
|
const modalReblog = () => dispatch(toggleReblog(status));
|
||||||
const boostModal = settings.get('boostModal');
|
|
||||||
if ((e && e.shiftKey) || !boostModal) {
|
if ((e && e.shiftKey) || !boostModal) {
|
||||||
modalReblog();
|
modalReblog();
|
||||||
} else {
|
} else {
|
||||||
|
@ -229,7 +228,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
|
|
||||||
const doDeleteStatus = (withRedraft = false) => {
|
const doDeleteStatus = (withRedraft = false) => {
|
||||||
dispatch((_, getState) => {
|
dispatch((_, getState) => {
|
||||||
const deleteModal = settings.get('deleteModal');
|
|
||||||
if (!deleteModal) {
|
if (!deleteModal) {
|
||||||
dispatch(deleteStatus(status.id, withRedraft));
|
dispatch(deleteStatus(status.id, withRedraft));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -75,8 +75,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const settings = useSettings();
|
const { displayMedia, boostModal } = useSettings();
|
||||||
const displayMedia = settings.get('displayMedia') as string;
|
|
||||||
const didShowCard = useRef(false);
|
const didShowCard = useRef(false);
|
||||||
const node = useRef<HTMLDivElement>(null);
|
const node = useRef<HTMLDivElement>(null);
|
||||||
const overlay = useRef<HTMLDivElement>(null);
|
const overlay = useRef<HTMLDivElement>(null);
|
||||||
|
@ -155,7 +154,6 @@ const Status: React.FC<IStatus> = (props) => {
|
||||||
|
|
||||||
const handleHotkeyBoost = (e?: KeyboardEvent): void => {
|
const handleHotkeyBoost = (e?: KeyboardEvent): void => {
|
||||||
const modalReblog = () => dispatch(toggleReblog(actualStatus));
|
const modalReblog = () => dispatch(toggleReblog(actualStatus));
|
||||||
const boostModal = settings.get('boostModal');
|
|
||||||
if ((e && e.shiftKey) || !boostModal) {
|
if ((e && e.shiftKey) || !boostModal) {
|
||||||
modalReblog();
|
modalReblog();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -38,12 +38,11 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
const { displayMedia, deleteModal } = useSettings();
|
||||||
const { links } = useSoapboxConfig();
|
const { links } = useSoapboxConfig();
|
||||||
|
|
||||||
const isUnderReview = status.visibility === 'self';
|
const isUnderReview = status.visibility === 'self';
|
||||||
const isOwnStatus = status.getIn(['account', 'id']) === account?.id;
|
const isOwnStatus = status.getIn(['account', 'id']) === account?.id;
|
||||||
const displayMedia = settings.get('displayMedia') as string;
|
|
||||||
|
|
||||||
const [visible, setVisible] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
const [visible, setVisible] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
||||||
|
|
||||||
|
@ -58,7 +57,6 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteStatus = () => {
|
const handleDeleteStatus = () => {
|
||||||
const deleteModal = settings.get('deleteModal');
|
|
||||||
if (!deleteModal) {
|
if (!deleteModal) {
|
||||||
dispatch(deleteStatus(status.id, false));
|
dispatch(deleteStatus(status.id, false));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -22,8 +22,7 @@ export interface IStillImage {
|
||||||
|
|
||||||
/** Renders images on a canvas, only playing GIFs if autoPlayGif is enabled. */
|
/** 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 StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterboxed = false, showExt = false, onError }) => {
|
||||||
const settings = useSettings();
|
const { autoPlayGif } = useSettings();
|
||||||
const autoPlayGif = settings.get('autoPlayGif');
|
|
||||||
|
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const img = useRef<HTMLImageElement>(null);
|
const img = useRef<HTMLImageElement>(null);
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { toggleMainWindow } from 'soapbox/actions/chats';
|
||||||
import { useAppDispatch, useOwnAccount, useSettings } from 'soapbox/hooks';
|
import { useAppDispatch, useOwnAccount, useSettings } from 'soapbox/hooks';
|
||||||
import { IChat, useChat } from 'soapbox/queries/chats';
|
import { IChat, useChat } from 'soapbox/queries/chats';
|
||||||
|
|
||||||
type WindowState = 'open' | 'minimized';
|
|
||||||
|
|
||||||
const ChatContext = createContext<any>({
|
const ChatContext = createContext<any>({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
needsAcceptance: false,
|
needsAcceptance: false,
|
||||||
|
@ -26,7 +24,7 @@ interface IChatProvider {
|
||||||
const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
|
const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const settings = useSettings();
|
const { chats } = useSettings();
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
|
|
||||||
const path = history.location.pathname;
|
const path = history.location.pathname;
|
||||||
|
@ -38,9 +36,8 @@ const ChatProvider: React.FC<IChatProvider> = ({ children }) => {
|
||||||
|
|
||||||
const { data: chat } = useChat(currentChatId as string);
|
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 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) => {
|
const changeScreen = (screen: ChatWidgetScreens, currentChatId?: string | null) => {
|
||||||
setCurrentChatId(currentChatId || null);
|
setCurrentChatId(currentChatId || null);
|
||||||
|
|
|
@ -18,7 +18,7 @@ const AboutPage: React.FC = () => {
|
||||||
const soapboxConfig = useSoapboxConfig();
|
const soapboxConfig = useSoapboxConfig();
|
||||||
|
|
||||||
const [pageHtml, setPageHtml] = useState<string>('');
|
const [pageHtml, setPageHtml] = useState<string>('');
|
||||||
const [locale, setLocale] = useState<string>(settings.get('locale'));
|
const [locale, setLocale] = useState<string>(settings.locale);
|
||||||
|
|
||||||
const { aboutPages } = soapboxConfig;
|
const { aboutPages } = soapboxConfig;
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,7 @@ interface IMediaItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
|
const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
|
||||||
const settings = useSettings();
|
const { autoPlayGif, displayMedia } = useSettings();
|
||||||
const autoPlayGif = settings.get('autoPlayGif');
|
|
||||||
const displayMedia = settings.get('displayMedia');
|
|
||||||
|
|
||||||
const [visible, setVisible] = useState<boolean>(displayMedia !== 'hide_all' && !attachment.status?.sensitive || displayMedia === 'show_all');
|
const [visible, setVisible] = useState<boolean>(displayMedia !== 'hide_all' && !attachment.status?.sensitive || displayMedia === 'show_all');
|
||||||
|
|
||||||
const handleMouseEnter: React.MouseEventHandler<HTMLVideoElement> = e => {
|
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 [accountLoading, setAccountLoading] = useState<boolean>(!account);
|
||||||
|
|
||||||
const path = withReplies ? `${account?.id}:with_replies` : account?.id;
|
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 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' }));
|
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 history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const settings = useSettings();
|
const { locale } = useSettings();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
|
|
||||||
const locale = settings.get('locale');
|
|
||||||
const needsConfirmation = instance.pleroma.metadata.account_activation_required;
|
const needsConfirmation = instance.pleroma.metadata.account_activation_required;
|
||||||
const needsApproval = instance.registrations.approval_required;
|
const needsApproval = instance.registrations.approval_required;
|
||||||
const supportsEmailList = features.emailList;
|
const supportsEmailList = features.emailList;
|
||||||
|
|
|
@ -18,7 +18,7 @@ const CommunityTimeline = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const settings = useSettings();
|
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 next = useAppSelector(state => state.timelines.get('community')?.next);
|
||||||
|
|
||||||
const timelineId = 'community';
|
const timelineId = 'community';
|
||||||
|
|
|
@ -71,7 +71,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const settings = useSettings();
|
const { boostModal } = useSettings();
|
||||||
const { account: ownAccount } = useOwnAccount();
|
const { account: ownAccount } = useOwnAccount();
|
||||||
const isStaff = ownAccount ? ownAccount.staff : false;
|
const isStaff = ownAccount ? ownAccount.staff : false;
|
||||||
const isAdmin = ownAccount ? ownAccount.admin : false;
|
const isAdmin = ownAccount ? ownAccount.admin : false;
|
||||||
|
@ -123,7 +123,6 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
|
|
||||||
const handleReblogClick = () => {
|
const handleReblogClick = () => {
|
||||||
const modalReblog = () => dispatch(toggleReblog(status));
|
const modalReblog = () => dispatch(toggleReblog(status));
|
||||||
const boostModal = settings.get('boostModal');
|
|
||||||
if (!boostModal) {
|
if (!boostModal) {
|
||||||
modalReblog();
|
modalReblog();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -28,8 +28,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
||||||
const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity;
|
const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity;
|
||||||
|
|
||||||
const { tileServer } = useSoapboxConfig();
|
const { tileServer } = useSoapboxConfig();
|
||||||
const settings = useSettings();
|
const { displayMedia } = useSettings();
|
||||||
const displayMedia = settings.get('displayMedia') as string;
|
|
||||||
|
|
||||||
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
|
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
|
||||||
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
||||||
|
|
|
@ -25,8 +25,8 @@ const NotificationFilterBar = () => {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
|
||||||
const selectedFilter = settings.getIn(['notifications', 'quickFilter', 'active']) as string;
|
const selectedFilter = settings.notifications.quickFilter.active;
|
||||||
const advancedMode = settings.getIn(['notifications', 'quickFilter', 'advanced']);
|
const advancedMode = settings.notifications.quickFilter.advanced;
|
||||||
|
|
||||||
const onClick = (notificationType: string) => () => dispatch(setFilter(notificationType));
|
const onClick = (notificationType: string) => () => dispatch(setFilter(notificationType));
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
|
import get from 'lodash/get';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Toggle } from 'soapbox/components/ui';
|
import { Toggle } from 'soapbox/components/ui';
|
||||||
|
import { Settings } from 'soapbox/schemas/soapbox/settings';
|
||||||
import type { Map as ImmutableMap } from 'immutable';
|
|
||||||
|
|
||||||
interface ISettingToggle {
|
interface ISettingToggle {
|
||||||
/** Unique identifier for the Toggle. */
|
/** Unique identifier for the Toggle. */
|
||||||
id?: string;
|
id?: string;
|
||||||
/** The full user settings map. */
|
/** The full user settings map. */
|
||||||
settings: ImmutableMap<string, any>;
|
settings: Settings;
|
||||||
/** Array of key names leading into the setting map. */
|
/** Array of key names leading into the setting map. */
|
||||||
settingPath: string[];
|
settingPath: string[];
|
||||||
/** Callback when the setting is toggled. */
|
/** Callback when the setting is toggled. */
|
||||||
|
@ -25,7 +25,7 @@ const SettingToggle: React.FC<ISettingToggle> = ({ id, settings, settingPath, on
|
||||||
return (
|
return (
|
||||||
<Toggle
|
<Toggle
|
||||||
id={id}
|
id={id}
|
||||||
checked={!!settings.getIn(settingPath)}
|
checked={!!get(settings, settingPath)}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -50,8 +50,8 @@ const Notifications = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
const showFilterBar = settings.getIn(['notifications', 'quickFilter', 'show']);
|
const showFilterBar = settings.notifications.quickFilter.show;
|
||||||
const activeFilter = settings.getIn(['notifications', 'quickFilter', 'active']);
|
const activeFilter = settings.notifications.quickFilter.active;
|
||||||
const notifications = useAppSelector(state => getNotifications(state));
|
const notifications = useAppSelector(state => getNotifications(state));
|
||||||
const isLoading = useAppSelector(state => state.notifications.isLoading);
|
const isLoading = useAppSelector(state => state.notifications.isLoading);
|
||||||
// const isUnread = useAppSelector(state => state.notifications.unread > 0);
|
// const isUnread = useAppSelector(state => state.notifications.unread > 0);
|
||||||
|
|
|
@ -139,7 +139,7 @@ const Preferences = () => {
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
className='max-w-[200px]'
|
className='max-w-[200px]'
|
||||||
items={languages}
|
items={languages}
|
||||||
defaultValue={settings.get('locale') as string | undefined}
|
defaultValue={settings.locale}
|
||||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['locale'])}
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['locale'])}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -148,7 +148,7 @@ const Preferences = () => {
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
className='max-w-[200px]'
|
className='max-w-[200px]'
|
||||||
items={displayMediaOptions}
|
items={displayMediaOptions}
|
||||||
defaultValue={settings.get('displayMedia') as string | undefined}
|
defaultValue={settings.displayMedia}
|
||||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['displayMedia'])}
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['displayMedia'])}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -158,7 +158,7 @@ const Preferences = () => {
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
className='max-w-[200px]'
|
className='max-w-[200px]'
|
||||||
items={defaultPrivacyOptions}
|
items={defaultPrivacyOptions}
|
||||||
defaultValue={settings.get('defaultPrivacy') as string | undefined}
|
defaultValue={settings.defaultPrivacy}
|
||||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultPrivacy'])}
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultPrivacy'])}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -169,7 +169,7 @@ const Preferences = () => {
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
className='max-w-[200px]'
|
className='max-w-[200px]'
|
||||||
items={defaultContentTypeOptions}
|
items={defaultContentTypeOptions}
|
||||||
defaultValue={settings.get('defaultContentType') as string | undefined}
|
defaultValue={settings.defaultContentType}
|
||||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultContentType'])}
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => onSelectChange(event, ['defaultContentType'])}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
|
@ -23,13 +23,13 @@ const CommunityTimeline = () => {
|
||||||
|
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const settings = useSettings();
|
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 next = useAppSelector(state => state.timelines.get('public')?.next);
|
||||||
|
|
||||||
const timelineId = 'public';
|
const timelineId = 'public';
|
||||||
|
|
||||||
const explanationBoxExpanded = settings.get('explanationBox');
|
const explanationBoxExpanded = settings.explanationBox;
|
||||||
const showExplanationBox = settings.get('showExplanationBox');
|
const showExplanationBox = settings.showExplanationBox;
|
||||||
|
|
||||||
const dismissExplanationBox = () => {
|
const dismissExplanationBox = () => {
|
||||||
dispatch(changeSetting(['showExplanationBox'], false));
|
dispatch(changeSetting(['showExplanationBox'], false));
|
||||||
|
|
|
@ -10,9 +10,9 @@ interface IPinnedHostsPicker {
|
||||||
|
|
||||||
const PinnedHostsPicker: React.FC<IPinnedHostsPicker> = ({ host: activeHost }) => {
|
const PinnedHostsPicker: React.FC<IPinnedHostsPicker> = ({ host: activeHost }) => {
|
||||||
const settings = useSettings();
|
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 (
|
return (
|
||||||
<HStack className='mb-4' space={2}>
|
<HStack className='mb-4' space={2}>
|
||||||
|
|
|
@ -27,10 +27,10 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
const timelineId = 'remote';
|
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 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 = () => {
|
const handleCloseClick: React.MouseEventHandler = () => {
|
||||||
history.push('/timeline/fediverse');
|
history.push('/timeline/fediverse');
|
||||||
|
|
|
@ -26,8 +26,6 @@ import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/statu
|
||||||
import DetailedStatus from './detailed-status';
|
import DetailedStatus from './detailed-status';
|
||||||
import ThreadStatus from './thread-status';
|
import ThreadStatus from './thread-status';
|
||||||
|
|
||||||
type DisplayMedia = 'default' | 'hide_all' | 'show_all';
|
|
||||||
|
|
||||||
const getAncestorsIds = createSelector([
|
const getAncestorsIds = createSelector([
|
||||||
(_: RootState, statusId: string | undefined) => statusId,
|
(_: RootState, statusId: string | undefined) => statusId,
|
||||||
(state: RootState) => state.contexts.inReplyTos,
|
(state: RootState) => state.contexts.inReplyTos,
|
||||||
|
@ -96,9 +94,8 @@ const Thread = (props: IThread) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
const { displayMedia } = useSettings();
|
||||||
|
|
||||||
const displayMedia = settings.get('displayMedia') as DisplayMedia;
|
|
||||||
const isUnderReview = status?.visibility === 'self';
|
const isUnderReview = status?.visibility === 'self';
|
||||||
|
|
||||||
const { ancestorsIds, descendantsIds } = useAppSelector((state) => {
|
const { ancestorsIds, descendantsIds } = useAppSelector((state) => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ const InstanceInfoPanel: React.FC<IInstanceInfoPanel> = ({ host }) => {
|
||||||
|
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const remoteInstance: any = useAppSelector(state => getRemoteInstance(state, host));
|
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 = () => {
|
const handlePinHost = () => {
|
||||||
if (!pinned) {
|
if (!pinned) {
|
||||||
|
|
|
@ -7,10 +7,9 @@ import { useInstance, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||||
const PromoPanel: React.FC = () => {
|
const PromoPanel: React.FC = () => {
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const { promoPanel } = useSoapboxConfig();
|
const { promoPanel } = useSoapboxConfig();
|
||||||
const settings = useSettings();
|
const { locale } = useSettings();
|
||||||
|
|
||||||
const promoItems = promoPanel.get('items');
|
const promoItems = promoPanel.get('items');
|
||||||
const locale = settings.get('locale');
|
|
||||||
|
|
||||||
if (!promoItems || promoItems.isEmpty()) return null;
|
if (!promoItems || promoItems.isEmpty()) return null;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import ThemeSelector from './theme-selector';
|
||||||
/** Stateful theme selector. */
|
/** Stateful theme selector. */
|
||||||
const ThemeToggle: React.FC = () => {
|
const ThemeToggle: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const themeMode = useSettings().get('themeMode');
|
const { themeMode } = useSettings();
|
||||||
|
|
||||||
const handleChange = (themeMode: string) => {
|
const handleChange = (themeMode: string) => {
|
||||||
dispatch(changeSetting(['themeMode'], themeMode));
|
dispatch(changeSetting(['themeMode'], themeMode));
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { useSettings } from 'soapbox/hooks';
|
||||||
import ReducedMotion from './reduced-motion';
|
import ReducedMotion from './reduced-motion';
|
||||||
|
|
||||||
const OptionalMotion = (props: MotionProps) => {
|
const OptionalMotion = (props: MotionProps) => {
|
||||||
const reduceMotion = useSettings().get('reduceMotion');
|
const { reduceMotion } = useSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
reduceMotion ? <ReducedMotion {...props} /> : <Motion {...props} />
|
reduceMotion ? <ReducedMotion {...props} /> : <Motion {...props} />
|
||||||
|
|
|
@ -43,7 +43,7 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
const settings = useSettings();
|
const { isDeveloper } = useSettings();
|
||||||
|
|
||||||
const renderComponent = ({ match }: RouteComponentProps) => {
|
const renderComponent = ({ match }: RouteComponentProps) => {
|
||||||
if (Page) {
|
if (Page) {
|
||||||
|
@ -81,7 +81,7 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
|
||||||
|
|
||||||
const authorized = [
|
const authorized = [
|
||||||
account || publicRoute,
|
account || publicRoute,
|
||||||
developerOnly ? settings.get('isDeveloper') : true,
|
developerOnly ? isDeveloper : true,
|
||||||
staffOnly ? account && account.staff : true,
|
staffOnly ? account && account.staff : true,
|
||||||
adminOnly ? account && account.admin : true,
|
adminOnly ? account && account.admin : true,
|
||||||
].every(c => c);
|
].every(c => c);
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import { settingsSchema } from 'soapbox/schemas/soapbox/settings';
|
||||||
|
|
||||||
import { useAppSelector } from './useAppSelector';
|
import { useAppSelector } from './useAppSelector';
|
||||||
|
|
||||||
import type { Map as ImmutableMap } from 'immutable';
|
|
||||||
|
|
||||||
/** Get the user settings from the store */
|
/** Get the user settings from the store */
|
||||||
export const useSettings = (): ImmutableMap<string, any> => {
|
export const useSettings = () => {
|
||||||
return useAppSelector((state) => getSettings(state));
|
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.
|
* regardless of whether that's by system theme or direct setting.
|
||||||
*/
|
*/
|
||||||
const useTheme = (): Theme => {
|
const useTheme = (): Theme => {
|
||||||
const settings = useSettings();
|
const { themeMode } = useSettings();
|
||||||
const systemTheme = useSystemTheme();
|
const systemTheme = useSystemTheme();
|
||||||
|
|
||||||
const userTheme = settings.get('themeMode');
|
const darkMode = themeMode === 'dark' || (themeMode === 'system' && systemTheme === 'dark');
|
||||||
const darkMode = userTheme === 'dark' || (userTheme === 'system' && systemTheme === 'dark');
|
|
||||||
|
|
||||||
return darkMode ? 'dark' : 'light';
|
return darkMode ? 'dark' : 'light';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,18 +20,17 @@ interface ISoapboxHead {
|
||||||
/** Injects metadata into site head with Helmet. */
|
/** Injects metadata into site head with Helmet. */
|
||||||
const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
|
const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
|
||||||
const { locale, direction } = useLocale();
|
const { locale, direction } = useLocale();
|
||||||
const settings = useSettings();
|
const { demo, reduceMotion, underlineLinks, demetricator } = useSettings();
|
||||||
const soapboxConfig = useSoapboxConfig();
|
const soapboxConfig = useSoapboxConfig();
|
||||||
|
|
||||||
const demo = !!settings.get('demo');
|
|
||||||
const darkMode = useTheme() === 'dark';
|
const darkMode = useTheme() === 'dark';
|
||||||
const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig);
|
const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig);
|
||||||
const dsn = soapboxConfig.sentryDsn;
|
const dsn = soapboxConfig.sentryDsn;
|
||||||
|
|
||||||
const bodyClass = clsx('h-full bg-white text-base dark:bg-gray-800', {
|
const bodyClass = clsx('h-full bg-white text-base dark:bg-gray-800', {
|
||||||
'no-reduce-motion': !settings.get('reduceMotion'),
|
'no-reduce-motion': !reduceMotion,
|
||||||
'underline-links': settings.get('underlineLinks'),
|
'underline-links': underlineLinks,
|
||||||
'demetricator': settings.get('demetricator'),
|
'demetricator': demetricator,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { z } from 'zod';
|
||||||
|
|
||||||
import { locales } from 'soapbox/messages';
|
import { locales } from 'soapbox/messages';
|
||||||
|
|
||||||
|
import { coerceObject } from '../utils';
|
||||||
|
|
||||||
const skinToneSchema = z.union([
|
const skinToneSchema = z.union([
|
||||||
z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5), z.literal(6),
|
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),
|
autoPlayGif: z.boolean().catch(true),
|
||||||
displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'),
|
displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'),
|
||||||
expandSpoilers: z.boolean().catch(false),
|
expandSpoilers: z.boolean().catch(false),
|
||||||
|
preserveSpoilers: z.boolean().catch(false),
|
||||||
unfollowModal: z.boolean().catch(false),
|
unfollowModal: z.boolean().catch(false),
|
||||||
boostModal: z.boolean().catch(false),
|
boostModal: z.boolean().catch(false),
|
||||||
deleteModal: z.boolean().catch(true),
|
deleteModal: z.boolean().catch(true),
|
||||||
|
@ -29,6 +32,47 @@ const settingsSchema = z.object({
|
||||||
systemFont: z.boolean().catch(false),
|
systemFont: z.boolean().catch(false),
|
||||||
demetricator: z.boolean().catch(false),
|
demetricator: z.boolean().catch(false),
|
||||||
isDeveloper: 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>;
|
type Settings = z.infer<typeof settingsSchema>;
|
||||||
|
|
Loading…
Reference in a new issue