From 25209356760d40ff787019010ce8d7017e2ed04a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 24 Mar 2026 13:59:47 +0200 Subject: [PATCH 1/5] lint --- src/modules/default_config_state.js | 5 +++-- src/stores/instance.js | 2 +- src/stores/sync_config.js | 4 +++- test/unit/specs/stores/sync_config.spec.js | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index 8634752bd..429bdd931 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -5,9 +5,9 @@ const browserLocale = (navigator.language || 'en').split('-')[0] /// to override it. export const staticOrApiConfigDefault = { name: 'PleromaFE', - theme: 'pleroma-dark', + theme: null, palette: null, - style: null, + style: 'breezy', themeChecksum: undefined, defaultAvatar: '/images/avi.png', defaultBanner: '/images/banner.png', @@ -189,6 +189,7 @@ export const defaultConfigLocal = { themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists } + export const LOCAL_ONLY_KEYS = new Set(Object.keys(defaultConfigLocal)) export const makeUndefined = (c) => diff --git a/src/stores/instance.js b/src/stores/instance.js index c893cab64..0838f7a51 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -3,8 +3,8 @@ import { defineStore } from 'pinia' import { instanceDefaultProperties } from '../modules/config.js' import { - instanceDefaultConfig, defaultConfigLocal, + instanceDefaultConfig, staticOrApiConfigDefault, } from '../modules/default_config_state.js' import apiService from '../services/api/api.service.js' diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index 84011e2cf..d4b5ad50a 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -643,7 +643,9 @@ export const useSyncConfigStore = defineStore('sync_config', { vuexState.config = vuexState.config ?? {} const migratedEntries = new Set(vuexState.config._syncMigration ?? []) - console.debug(`Already migrated Values: ${[...migratedEntries].join() || '[none]'}`) + console.debug( + `Already migrated Values: ${[...migratedEntries].join() || '[none]'}`, + ) Object.entries(oldDefaultConfigSync).forEach(([key, value]) => { const oldValue = vuexState.config[key] diff --git a/test/unit/specs/stores/sync_config.spec.js b/test/unit/specs/stores/sync_config.spec.js index 8078b5949..eef6e39ac 100644 --- a/test/unit/specs/stores/sync_config.spec.js +++ b/test/unit/specs/stores/sync_config.spec.js @@ -1,6 +1,8 @@ import { cloneDeep } from 'lodash' import { createPinia, setActivePinia } from 'pinia' +import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' + import { _getAllFlags, _getRecentData, @@ -15,7 +17,6 @@ import { useSyncConfigStore, VERSION, } from 'src/stores/sync_config.js' -import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' describe('The SyncConfig store', () => { beforeEach(() => { From a1cde6ce0fcc73f2545ca8aab8ece77ba6723189 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 24 Mar 2026 14:37:51 +0200 Subject: [PATCH 2/5] renamed identity stuff to be more clear --- src/boot/after_store.js | 4 ++-- src/modules/default_config_state.js | 5 ++--- src/stores/instance.js | 5 ++--- src/stores/interface.js | 18 ++++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index fe21eb442..0cf25ce5d 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -40,7 +40,7 @@ import { useUserHighlightStore } from 'src/stores/user_highlight.js' import VBodyScrollLock from 'src/directives/body_scroll_lock' import { instanceDefaultConfig, - staticOrApiConfigDefault, + instanceIdentityDefault, } from 'src/modules/default_config_state.js' let staticInitialResults = null @@ -175,7 +175,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { } } - Object.keys(staticOrApiConfigDefault) + Object.keys(instanceIdentityDefault) .map((k) => ({ source: k, destination: `instanceIdentity.${k}` })) .forEach(copyInstanceOption) Object.keys(instanceDefaultConfig) diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index 429bdd931..1fe194aab 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -3,11 +3,10 @@ const browserLocale = (navigator.language || 'en').split('-')[0] /// Instance config entries provided by static config or pleroma api /// Put settings here only if it does not make sense for a normal user /// to override it. -export const staticOrApiConfigDefault = { - name: 'PleromaFE', +export const instanceIdentityDefault = { theme: null, palette: null, - style: 'breezy', + style: null, themeChecksum: undefined, defaultAvatar: '/images/avi.png', defaultBanner: '/images/banner.png', diff --git a/src/stores/instance.js b/src/stores/instance.js index 0838f7a51..f1f31a8d1 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -5,7 +5,7 @@ import { instanceDefaultProperties } from '../modules/config.js' import { defaultConfigLocal, instanceDefaultConfig, - staticOrApiConfigDefault, + instanceIdentityDefault, } from '../modules/default_config_state.js' import apiService from '../services/api/api.service.js' @@ -16,7 +16,6 @@ import { ensureFinalFallback } from 'src/i18n/languages.js' const REMOTE_INTERACTION_URL = '/main/ostatus' const defaultState = { - // Stuff from apiConfig name: 'Pleroma FE', registrationOpen: true, server: 'http://localhost:4040/', @@ -36,7 +35,7 @@ const defaultState = { // Instance-wide configurations that should not be changed by individual users instanceIdentity: { - ...staticOrApiConfigDefault, + ...instanceIdentityDefault, }, limits: { diff --git a/src/stores/interface.js b/src/stores/interface.js index 32a8226b4..caccd62b5 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -412,11 +412,13 @@ export const useInterfaceStore = defineStore('interface', { return result } - const { style: instanceStyleName, palette: instancePaletteName } = - useInstanceStore() + let { + theme: instanceThemeName, + style: instanceStyleName, + palette: instancePaletteName, + } = useInstanceStore().instanceIdentity let { - theme: instanceThemeV2Name, themesIndex, stylesIndex, palettesIndex, @@ -447,7 +449,7 @@ export const useInterfaceStore = defineStore('interface', { console.debug( `Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`, ) - console.debug('Instance V2 theme: ' + instanceThemeV2Name) + console.debug('Instance V2 theme: ' + instanceThemeName) if ( userPaletteName || @@ -456,11 +458,11 @@ export const useInterfaceStore = defineStore('interface', { userStyleCustomData || // User V2 overrides instance V3 ((instancePaletteName || instanceStyleName) && - instanceThemeV2Name == null && + instanceThemeName == null && userThemeV2Name == null) ) { // Palette and/or style overrides V2 themes - instanceThemeV2Name = null + instanceThemeName = null userThemeV2Name = null userThemeV2Source = null userThemeV2Snapshot = null @@ -470,7 +472,7 @@ export const useInterfaceStore = defineStore('interface', { userThemeV2Name || userThemeV2Snapshot || userThemeV2Source || - instanceThemeV2Name + instanceThemeName ) { majorVersionUsed = 'v2' } else { @@ -588,7 +590,7 @@ export const useInterfaceStore = defineStore('interface', { 'theme', themesIndex, userThemeV2Source || userThemeV2Snapshot, - userThemeV2Name || instanceThemeV2Name, + userThemeV2Name || instanceThemeName, ) this.themeNameUsed = theme.nameUsed this.themeDataUsed = theme.dataUsed From 0e2a94bf346312e3dbac430f5574c299f60c9cc5 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 24 Mar 2026 14:44:05 +0200 Subject: [PATCH 3/5] should have done this long time ago --- src/App.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 19d28f843..5b1403828 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,6 @@ import { throttle } from 'lodash' import { mapState } from 'pinia' -import { defineAsyncComponent } from 'vue' +import { defineAsyncComponent, toValue } from 'vue' import DesktopNav from './components/desktop_nav/desktop_nav.vue' import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue' @@ -32,6 +32,9 @@ import { useSyncConfigStore } from 'src/stores/sync_config.js' import messages from 'src/i18n/messages' import localeService from 'src/services/locale/locale.service.js' +// Helper to unwrap reactive proxies +window.toValue = toValue + export default { name: 'app', components: { From f57c24cf6df4627177e2b1348355d733d785f77b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 24 Mar 2026 15:13:24 +0200 Subject: [PATCH 4/5] instance identity config definitions --- src/boot/after_store.js | 19 +++-- src/modules/default_config_state.js | 124 +++++++++++++++++++++++----- src/stores/interface.js | 6 +- 3 files changed, 115 insertions(+), 34 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 0cf25ce5d..df641f67b 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -40,7 +40,7 @@ import { useUserHighlightStore } from 'src/stores/user_highlight.js' import VBodyScrollLock from 'src/directives/body_scroll_lock' import { instanceDefaultConfig, - instanceIdentityDefault, + instanceIdentityDefaultDefinition, } from 'src/modules/default_config_state.js' let staticInitialResults = null @@ -169,17 +169,20 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { config = Object.assign({}, staticConfig, apiConfig) } - const copyInstanceOption = ({ source, destination }) => { - if (typeof config[source] !== 'undefined') { - useInstanceStore().set({ path: destination, value: config[source] }) - } + const copyInstanceOption = ({ source, definition = { required: true }, destination }) => { + const value = config[source] + const { required, type } = definition + if (required && value == null) return + if (type != null && typeof value !== type) return + + useInstanceStore().set({ path: destination, value }) } - Object.keys(instanceIdentityDefault) - .map((k) => ({ source: k, destination: `instanceIdentity.${k}` })) + Object.entries(instanceIdentityDefaultDefinition) + .map(([source, definition]) => ({ source, definition, destination: `instanceIdentity.${source}` })) .forEach(copyInstanceOption) Object.keys(instanceDefaultConfig) - .map((k) => ({ source: k, destination: `prefsStorage.${k}` })) + .map((source) => ({ source, destination: `prefsStorage.${source}` })) .forEach(copyInstanceOption) useAuthFlowStore().setInitialStrategy(config.loginMethod) diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index 1fe194aab..79708430c 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -3,30 +3,112 @@ const browserLocale = (navigator.language || 'en').split('-')[0] /// Instance config entries provided by static config or pleroma api /// Put settings here only if it does not make sense for a normal user /// to override it. -export const instanceIdentityDefault = { - theme: null, - palette: null, - style: null, - themeChecksum: undefined, - defaultAvatar: '/images/avi.png', - defaultBanner: '/images/banner.png', - background: '/static/aurora_borealis.jpg', - embeddedToS: true, - logo: '/static/logo.svg', - logoMargin: '.2em', - logoMask: true, - logoLeft: false, - redirectRootLogin: '/main/friends', - redirectRootNoLogin: '/main/all', - hideSitename: false, - nsfwCensorImage: null, - showFeaturesPanel: true, - showInstanceSpecificPanel: false, +export const instanceIdentityDefaultDefinition = { + style: { + description: 'Instance default style name', + type: 'string', + required: false, + }, + palette: { + description: 'Instance default palette name', + type: 'string', + required: false, + }, + theme: { + description: 'Instance default theme name', + type: 'string', + required: false, + }, + defaultAvatar: { + description: "Default avatar image to use when user doesn't have one set", + type: 'string', + default: '/images/avi.png', + }, + defaultBanner: { + description: "Default banner image to use when user doesn't have one set", + type: 'string', + default: '/images/banner.png', + }, + background: { + description: 'Instance background/wallpaper', + type: 'string', + default: '/static/aurora_borealis.jpg', + }, + embeddedToS: { + description: 'Whether to show Terms of Service title bar', + type: 'boolean', + default: true, + }, + logo: { + description: 'Instance logo', + type: 'string', + default: '/static/logo.svg', + }, + logoMargin: { + description: 'Margin for logo (spacing above/below)', + type: 'string', + default: '.2em', + }, + logoMask: { + description: + 'Use logo as a mask (works well for monochrome/transparent logos)', + type: 'boolean', + default: true, + }, + logoLeft: { + description: 'Show logo on the left side of navbar', + type: 'boolean', + default: false, + }, + redirectRootLogin: { + description: 'Where to redirect user after login', + type: 'string', + default: '/main/friends', + }, + redirectRootNoLogin: { + description: 'Where to redirect anonymous visitors', + type: 'string', + default: '/main/all', + }, + hideSitename: { + description: 'Hide the instance name in navbar', + type: 'boolean', + default: false, + }, + nsfwCensorImage: { + description: 'Default NSFW censor image', + type: 'string', + required: false, + }, + showFeaturesPanel: { + description: 'Show features panel to anonymous visitors', + type: 'boolean', + default: true, + }, + showInstanceSpecificPanel: { + description: 'Show instance-specific panel', + type: 'boolean', + default: false, + }, // Html stuff - instanceSpecificPanelContent: '', - tos: '', + instanceSpecificPanelContent: { + description: 'HTML of Instance-specific panel', + type: 'string', + required: false, + }, + tos: { + description: 'HTML of Terms of Service panel', + type: 'string', + required: false, + }, } +export const instanceIdentityDefault = Object.fromEntries( + Object.entries(instanceIdentityDefaultDefinition).map(([k, v]) => [ + k, + v.default == null ? null : v.default, + ]), +) /// This object contains setting entries that makes sense /// at the user level. The defaults can also be overriden by diff --git a/src/stores/interface.js b/src/stores/interface.js index caccd62b5..f5a6c7d8a 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -418,11 +418,7 @@ export const useInterfaceStore = defineStore('interface', { palette: instancePaletteName, } = useInstanceStore().instanceIdentity - let { - themesIndex, - stylesIndex, - palettesIndex, - } = useInstanceStore() + let { themesIndex, stylesIndex, palettesIndex } = useInstanceStore() const { style: userStyleName, From 694a1f01036c31a4afbbab0016a932e8f59a3435 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 24 Mar 2026 15:45:31 +0200 Subject: [PATCH 5/5] instance defaults definitions --- src/boot/after_store.js | 13 +- src/modules/default_config_state.js | 506 +++++++++++++++++++++------- 2 files changed, 395 insertions(+), 124 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index df641f67b..5c2aaa6ef 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -39,8 +39,8 @@ import { useUserHighlightStore } from 'src/stores/user_highlight.js' import VBodyScrollLock from 'src/directives/body_scroll_lock' import { - instanceDefaultConfig, - instanceIdentityDefaultDefinition, + instanceDefaultConfigDefinitions, + instanceIdentityDefaultDefinitions, } from 'src/modules/default_config_state.js' let staticInitialResults = null @@ -171,18 +171,19 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { const copyInstanceOption = ({ source, definition = { required: true }, destination }) => { const value = config[source] - const { required, type } = definition + let { required, type, default: defaultValue } = definition + if (type == null && defaultValue != null) type = typeof defaultValue if (required && value == null) return if (type != null && typeof value !== type) return useInstanceStore().set({ path: destination, value }) } - Object.entries(instanceIdentityDefaultDefinition) + Object.entries(instanceIdentityDefaultDefinitions) .map(([source, definition]) => ({ source, definition, destination: `instanceIdentity.${source}` })) .forEach(copyInstanceOption) - Object.keys(instanceDefaultConfig) - .map((source) => ({ source, destination: `prefsStorage.${source}` })) + Object.keys(instanceDefaultConfigDefinitions) + .map(([source, definition]) => ({ source, definition, destination: `prefsStorage.${source}` })) .forEach(copyInstanceOption) useAuthFlowStore().setInitialStrategy(config.loginMethod) diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index 79708430c..89047f314 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -1,9 +1,16 @@ const browserLocale = (navigator.language || 'en').split('-')[0] +const convertDefinitions = definitions => Object.fromEntries( + Object.entries(definitions).map(([k, v]) => [ + k, + v.default == null ? null : v.default, + ]), +) + /// Instance config entries provided by static config or pleroma api /// Put settings here only if it does not make sense for a normal user /// to override it. -export const instanceIdentityDefaultDefinition = { +export const instanceIdentityDefaultDefinitions = { style: { description: 'Instance default style name', type: 'string', @@ -103,142 +110,405 @@ export const instanceIdentityDefaultDefinition = { required: false, }, } -export const instanceIdentityDefault = Object.fromEntries( - Object.entries(instanceIdentityDefaultDefinition).map(([k, v]) => [ - k, - v.default == null ? null : v.default, - ]), -) +export const instanceIdentityDefault = convertDefinitions(instanceIdentityDefaultDefinitions) /// This object contains setting entries that makes sense /// at the user level. The defaults can also be overriden by /// instance admins in the frontend_configuration endpoint or static config. -export const instanceDefaultConfig = { - expertLevel: 0, // used to track which settings to show and hide - hideISP: false, - hideInstanceWallpaper: false, - hideShoutbox: false, - // bad name: actually hides posts of muted USERS - hideMutedPosts: false, - hideMutedThreads: true, - hideWordFilteredPosts: false, - muteBotStatuses: false, - muteSensitiveStatuses: false, - collapseMessageWithSubject: false, - padEmoji: true, - hideAttachmentsInConv: false, - hideScrobbles: false, - hideScrobblesAfter: '2d', - maxThumbnails: 16, - loopVideo: true, - loopVideoSilentOnly: true, +export const instanceDefaultConfigDefinitions = { + expertLevel: { + description: 'Used to track which settings to show and hide in settings modal', + type: 'number', // not a boolean so we could potentially make multiple levels of expert-ness + default: 0, + }, + hideISP: { + description: 'Hide Instance-specific panel', + default: false, + }, + hideInstanceWallpaper: { + description: 'Hide Instance default background', + default: false, + }, + hideShoutbox: { + description: 'Hide shoutbox if present', + default: false, + }, + hideMutedPosts: { // bad name + description: 'Hide posts of muted users entirely', + default: false, + }, + hideMutedThreads: { + description: 'Hide muted threads entirely', + default: true, + }, + hideWordFilteredPosts: { + description: 'Hide wordfiltered posts entirely', + default: false, + }, + muteBotStatuses: { + description: 'Mute posts made by bots', + default: false, + }, + muteSensitiveStatuses: { + description: 'Mute posts marked as NSFW', + default: false, + }, + collapseMessageWithSubject: { + description: 'Collapse posts with subject', + default: false, + }, + padEmoji: { + description: 'Pad emoji with spaces when using emoji picker', + default: true, + }, + hideAttachmentsInConv: { + description: 'Hide attachments', + default: false, + }, + hideScrobbles: { + description: 'Hide scrobbles', + default: false, + }, + hideScrobblesAfter: { + description: 'Hide scrobbles older than', + default: '2d', + }, + maxThumbnails: { + description: 'Maximum attachments to show', + default: 16, + }, + loopVideo: { + description: 'Loop videos', + default: true, + }, + loopVideoSilentOnly: { + description: 'Loop only videos without sound', + default: true, + }, /// This is not the streaming API configuration, but rather an option /// for automatically loading new posts into the timeline without /// the user clicking the Show New button. - streaming: false, - emojiReactionsOnTimeline: true, - alwaysShowNewPostButton: false, - autohideFloatingPostButton: false, - pauseOnUnfocused: true, - stopGifs: true, - replyVisibility: 'all', - thirdColumnMode: 'notifications', + streaming: { + description: 'Automatically show new posts', + default: false, + }, + pauseOnUnfocused: { + description: 'Pause showing new posts when tab is unfocused', + default: true, + }, + emojiReactionsOnTimeline: { + description: 'Show emoji reactions on timeline', + default: true, + }, + alwaysShowNewPostButton: { + description: 'Always show mobile "new post" button, even in desktop mode', + default: false, + }, + autohideFloatingPostButton: { + description: 'Automatically hide mobile "new post" button when scrolling down', + default: false, + }, + stopGifs: { + description: 'Play animated gifs on hover only', + default: true, + }, + replyVisibility: { + description: 'Type of replies to show', + default: 'all', + }, + thirdColumnMode: { + description: 'What to display in third column', + default: 'notifications', + }, notificationVisibility: { - follows: true, - mentions: true, - statuses: true, - likes: true, - repeats: true, - moves: true, - emojiReactions: true, - followRequest: true, - reports: true, - chatMention: true, - polls: true, + description: 'What types of notifications to show', + default: { + follows: true, + mentions: true, + statuses: true, + likes: true, + repeats: true, + moves: true, + emojiReactions: true, + followRequest: true, + reports: true, + chatMention: true, + polls: true, + }, }, notificationNative: { - follows: true, - mentions: true, - statuses: true, - likes: false, - repeats: false, - moves: false, - emojiReactions: false, - followRequest: true, - reports: true, - chatMention: true, - polls: true, + description: 'What type of notifications to show desktop notification for', + default: { + follows: true, + mentions: true, + statuses: true, + likes: false, + repeats: false, + moves: false, + emojiReactions: false, + followRequest: true, + reports: true, + chatMention: true, + polls: true, + }, + }, + webPushNotifications: { + description: 'Use WebPush', + default: false, + }, + webPushAlwaysShowNotifications: { + description: 'Ignore filter when using WebPush', + default: false, + }, + interfaceLanguage: { + description: 'UI language', + default: browserLocale, + }, + hideScopeNotice: { + description: 'Hide scope notification', + default: false, + }, + scopeCopy: { + description: 'Copy scope like mastodon does', + default: true, + }, + subjectLineBehavior: { + description: 'How to treat subject line', + default: 'email', + }, + alwaysShowSubjectInput: { + description: 'Always show subject line field', + default: true, + }, + minimalScopesMode: { + description: 'Minimize amount of options shown in scope selector', + default: false, }, - webPushNotifications: false, - webPushAlwaysShowNotifications: false, - interfaceLanguage: browserLocale, - hideScopeNotice: false, - scopeCopy: true, - subjectLineBehavior: 'email', - alwaysShowSubjectInput: true, - minimalScopesMode: false, // This hides statuses filtered via a word filter - hideFilteredStatuses: false, + hideFilteredStatuses: { + description: 'Hide wordfiltered entirely', + default: false, + }, // Confirmations - modalOnRepeat: false, - modalOnUnfollow: false, - modalOnBlock: true, - modalOnMute: false, - modalOnMuteConversation: false, - modalOnMuteDomain: true, - modalOnDelete: true, - modalOnLogout: true, - modalOnApproveFollow: false, - modalOnDenyFollow: false, - modalOnRemoveUserFromFollowers: false, + modalOnRepeat: { + description: 'Show confirmation modal for repeat', + default: false, + }, + modalOnUnfollow: { + description: 'Show confirmation modal for unfollow', + default: false, + }, + modalOnBlock: { + description: 'Show confirmation modal for block', + default: true, + }, + modalOnMute: { + description: 'Show confirmation modal for mute', + default: false, + }, + modalOnMuteConversation: { + description: 'Show confirmation modal for mute conversation', + default: false, + }, + modalOnMuteDomain: { + description: 'Show confirmation modal for mute domain', + default: true, + }, + modalOnDelete: { + description: 'Show confirmation modal for delete', + default: true, + }, + modalOnLogout: { + description: 'Show confirmation modal for logout', + default: true, + }, + modalOnApproveFollow: { + description: 'Show confirmation modal for approve follow', + default: false, + }, + modalOnDenyFollow: { + description: 'Show confirmation modal for deny follow', + default: false, + }, + modalOnRemoveUserFromFollowers: { + description: 'Show confirmation modal for follower removal', + default: false, + }, // Expiry confirmations/default actions - onMuteDefaultAction: 'ask', - onBlockDefaultAction: 'ask', + onMuteDefaultAction: { + description: 'Default action when muting user', + default: 'ask', + }, + onBlockDefaultAction: { + description: 'Default action when blocking user', + default: 'ask', + }, - modalMobileCenter: false, - playVideosInModal: false, - useContainFit: true, - disableStickyHeaders: false, - showScrollbars: false, - userPopoverAvatarAction: 'open', - userPopoverOverlay: false, - userCardLeftJustify: false, - userCardHidePersonalMarks: false, - forcedRoundness: -1, - greentext: false, - mentionLinkShowTooltip: true, - mentionLinkShowAvatar: false, - mentionLinkFadeDomain: true, - mentionLinkShowYous: false, - mentionLinkBoldenYou: true, - hidePostStats: false, - hideBotIndication: false, - hideUserStats: false, - virtualScrolling: true, - sensitiveByDefault: false, - conversationDisplay: 'linear', - conversationTreeAdvanced: false, - conversationOtherRepliesButton: 'below', - conversationTreeFadeAncestors: false, - showExtraNotifications: true, - showExtraNotificationsTip: true, - showChatsInExtraNotifications: true, - showAnnouncementsInExtraNotifications: true, - showFollowRequestsInExtraNotifications: true, - maxDepthInThread: 6, - autocompleteSelect: false, - closingDrawerMarksAsSeen: true, - unseenAtTop: false, - ignoreInactionableSeen: false, - unsavedPostAction: 'confirm', - autoSaveDraft: false, - useAbsoluteTimeFormat: false, - absoluteTimeFormatMinAge: '0d', - absoluteTime12h: '24h', + modalMobileCenter: { + description: 'Center mobile dialogs vertically', + default: false, + }, + playVideosInModal: { + description: 'Play videos in gallery view', + default: false, + }, + useContainFit: { + description: 'Use object-fit: contain for attachments', + default: true, + }, + disableStickyHeaders: { + description: 'Disable sticky headers', + default: false, + }, + showScrollbars: { + description: 'Always show scrollbars', + default: false, + }, + userPopoverAvatarAction: { + description: 'What to do when clicking popover avatar', + default: 'open', + }, + userPopoverOverlay: { + description: 'Overlay user popover with centering on avatar', + default: false, + }, + userCardLeftJustify: { + description: 'Justify user bio to the left', + default: false, + }, + userCardHidePersonalMarks: { + description: 'Hide highlight/personal note in user view', + default: false, + }, + forcedRoundness: { + description: 'Force roundness of the theme', + default: -1, + }, + greentext: { + description: 'Highlight plaintext >quotes', + default: false, + }, + mentionLinkShowTooltip: { + description: 'Show tooltips for mention links', + default: true, + }, + mentionLinkShowAvatar: { + description: 'Show avatar next to mention link', + default: false, + }, + mentionLinkFadeDomain: { + description: 'Mute (fade) domain name in mention links if configured to show it', + default: true, + }, + mentionLinkShowYous: { + description: 'Show (you)s when you are mentioned', + default: false, + }, + mentionLinkBoldenYou: { + description: 'Boldern mentionlink of you', + default: true, + }, + hidePostStats: { + description: 'Hide post stats (rt, favs)', + default: false, + }, + hideBotIndication: { + description: 'Hide bot indicator', + default: false, + }, + hideUserStats: { + description: 'Hide user stats (followers etc)', + default: false, + }, + virtualScrolling: { + description: 'Timeline virtual scrolling', + default: true, + }, + sensitiveByDefault: { + description: 'Assume attachments are NSFW by default', + default: false, + }, + conversationDisplay: { + description: 'Style of conversation display', + default: 'linear', + }, + conversationTreeAdvanced: { + description: 'Advanced features of tree view conversation', + default: false, + }, + conversationOtherRepliesButton: { + description: 'Where to show "other replies" in tree conversation view', + default: 'below', + }, + conversationTreeFadeAncestors: { + description: 'Fade ancestors in tree conversation view', + default: false, + }, + showExtraNotifications: { + description: 'Show extra notifications (chats, announcements etc) in notification panel', + default: true, + }, + showExtraNotificationsTip: { + description: 'Show tip for extra notifications (that user can remove them)', + default: true, + }, + showChatsInExtraNotifications: { + description: 'Show chat messages in notifications', + default: true, + }, + showAnnouncementsInExtraNotifications: { + description: 'Show announcements in notifications', + default: true, + }, + showFollowRequestsInExtraNotifications: { + description: 'Show follow requests in notifications', + default: true, + }, + maxDepthInThread: { + description: 'Maximum depth in tree conversation view', + default: 6, + }, + autocompleteSelect: { + description: '', + default: false, + }, + closingDrawerMarksAsSeen: { + description: 'Closing mobile notification pane marks everything as seen', + default: true, + }, + unseenAtTop: { + description: 'Show unseen notifications above others', + default: false, + }, + ignoreInactionableSeen: { + description: 'Treat inactionable (fav, rt etc) notifications as "seen"', + default: false, + }, + unsavedPostAction: { + description: 'What to do if post is aborted', + default: 'confirm', + }, + autoSaveDraft: { + description: 'Save drafts automatically', + default: false, + }, + useAbsoluteTimeFormat: { + description: 'Use absolute time format', + default: false, + }, + absoluteTimeFormatMinAge: { + description: 'Show absolute time format only after this post age', + default: '0d', + }, + absoluteTime12h: { + description: 'Use 24h time format', + default: '24h', + }, } +export const instanceDefaultConfig = convertDefinitions(instanceDefaultConfigDefinitions) export const defaultConfigLocal = { hideAttachments: false,