restructure settings definitions

This commit is contained in:
Henry Jameson 2026-03-24 20:04:46 +02:00
commit a461068e40
9 changed files with 432 additions and 139 deletions

View file

@ -39,8 +39,8 @@ import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import VBodyScrollLock from 'src/directives/body_scroll_lock' import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { import {
instanceDefaultConfigDefinitions, INSTANCE_DEFAULT_CONFIG_DEFINITIONS,
instanceIdentityDefaultDefinitions, INSTANCE_IDENTITY_DEFAULT_DEFINITIONS,
} from 'src/modules/default_config_state.js' } from 'src/modules/default_config_state.js'
let staticInitialResults = null let staticInitialResults = null
@ -83,7 +83,7 @@ const getInstanceConfig = async ({ store }) => {
const res = await preloadFetch('/api/v1/instance') const res = await preloadFetch('/api/v1/instance')
if (res.ok) { if (res.ok) {
const data = await res.json() const data = await res.json()
const textlimit = data.max_toot_chars const textLimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key const vapidPublicKey = data.pleroma.vapid_public_key
useInstanceCapabilitiesStore().set( useInstanceCapabilitiesStore().set(
@ -91,8 +91,8 @@ const getInstanceConfig = async ({ store }) => {
data.pleroma, data.pleroma,
) )
useInstanceStore().set({ useInstanceStore().set({
path: 'textlimit', path: 'limits.textLimit',
value: textlimit, value: textLimit,
}) })
useInstanceStore().set({ useInstanceStore().set({
path: 'accountApprovalRequired', path: 'accountApprovalRequired',
@ -169,22 +169,19 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
config = Object.assign({}, staticConfig, apiConfig) config = Object.assign({}, staticConfig, apiConfig)
} }
const copyInstanceOption = ({ source, definition = { required: true }, destination }) => { Object.keys(INSTANCE_IDENTITY_DEFAULT_DEFINITIONS).forEach((source) =>
const value = config[source] useInstanceStore().set({
let { required, type, default: defaultValue } = definition value: config[source],
if (type == null && defaultValue != null) type = typeof defaultValue path: `instanceIdentity.${source}`,
if (required && value == null) return }),
if (type != null && typeof value !== type) return )
useInstanceStore().set({ path: destination, value }) Object.keys(INSTANCE_DEFAULT_CONFIG_DEFINITIONS).forEach((source) =>
} useInstanceStore().set({
value: config[source],
Object.entries(instanceIdentityDefaultDefinitions) path: `prefsStorage.${source}`,
.map(([source, definition]) => ({ source, definition, destination: `instanceIdentity.${source}` })) }),
.forEach(copyInstanceOption) )
Object.keys(instanceDefaultConfigDefinitions)
.map(([source, definition]) => ({ source, definition, destination: `prefsStorage.${source}` }))
.forEach(copyInstanceOption)
useAuthFlowStore().setInitialStrategy(config.loginMethod) useAuthFlowStore().setInitialStrategy(config.loginMethod)
} }
@ -194,7 +191,7 @@ const getTOS = async ({ store }) => {
const res = await window.fetch('/static/terms-of-service.html') const res = await window.fetch('/static/terms-of-service.html')
if (res.ok) { if (res.ok) {
const html = await res.text() const html = await res.text()
useInstanceStore().set({ name: 'instanceIdentity.tos', value: html }) useInstanceStore().set({ path: 'instanceIdentity.tos', value: html })
} else { } else {
throw res throw res
} }
@ -543,7 +540,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
typeof overrides.target !== 'undefined' typeof overrides.target !== 'undefined'
? overrides.target ? overrides.target
: window.location.origin : window.location.origin
useInstanceStore().set({ name: 'server', value: server }) useInstanceStore().set({ path: 'server', value: server })
await setConfig({ store }) await setConfig({ store })
try { try {

View file

@ -15,7 +15,7 @@ const FeaturesPanel = {
'mediaProxyAvailable', 'mediaProxyAvailable',
]), ]),
...mapState(useInstanceStore, { ...mapState(useInstanceStore, {
textlimit: (store) => store.limits.textlimit, textLimit: (store) => store.limits.textLimit,
uploadlimit: (store) => uploadlimit: (store) =>
fileSizeFormatService.fileSizeFormat(store.limits.uploadlimit), fileSizeFormatService.fileSizeFormat(store.limits.uploadlimit),
}), }),

View file

@ -24,7 +24,7 @@
{{ $t('features_panel.media_proxy') }} {{ $t('features_panel.media_proxy') }}
</li> </li>
<li>{{ $t('features_panel.scope_options') }}</li> <li>{{ $t('features_panel.scope_options') }}</li>
<li>{{ $t('features_panel.text_limit') }} = {{ textlimit }}</li> <li>{{ $t('features_panel.text_limit') }} = {{ textLimit }}</li>
<li>{{ $t('features_panel.upload_limit') }} = {{ uploadlimit.num }} {{ $t('upload.file_size_units.' + uploadlimit.unit) }}</li> <li>{{ $t('features_panel.upload_limit') }} = {{ uploadlimit.num }} {{ $t('upload.file_size_units.' + uploadlimit.unit) }}</li>
</ul> </ul>
</div> </div>

View file

@ -290,7 +290,7 @@ const PostStatusForm = {
return this.newStatus.spoilerText.length return this.newStatus.spoilerText.length
}, },
statusLengthLimit() { statusLengthLimit() {
return useInstanceStore().textlimit return useInstanceStore().limits.textLimit
}, },
hasStatusLengthLimit() { hasStatusLengthLimit() {
return this.statusLengthLimit > 0 return this.statusLengthLimit > 0

View file

@ -1,16 +1,19 @@
import { get, set } from 'lodash'
const browserLocale = (navigator.language || 'en').split('-')[0] const browserLocale = (navigator.language || 'en').split('-')[0]
const convertDefinitions = definitions => Object.fromEntries( export const convertDefinitions = (definitions) =>
Object.entries(definitions).map(([k, v]) => [ Object.fromEntries(
k, Object.entries(definitions).map(([k, v]) => {
v.default == null ? null : v.default, const defaultValue = v.default ?? null
]), return [k, defaultValue]
) }),
)
/// Instance config entries provided by static config or pleroma api /// Instance config entries provided by static config or pleroma api
/// Put settings here only if it does not make sense for a normal user /// Put settings here only if it does not make sense for a normal user
/// to override it. /// to override it.
export const instanceIdentityDefaultDefinitions = { export const INSTANCE_IDENTITY_DEFAULT_DEFINITIONS = {
style: { style: {
description: 'Instance default style name', description: 'Instance default style name',
type: 'string', type: 'string',
@ -110,14 +113,17 @@ export const instanceIdentityDefaultDefinitions = {
required: false, required: false,
}, },
} }
export const instanceIdentityDefault = convertDefinitions(instanceIdentityDefaultDefinitions) export const INSTANCE_IDENTITY_DEFAULT = convertDefinitions(
INSTANCE_IDENTITY_DEFAULT_DEFINITIONS,
)
/// This object contains setting entries that makes sense /// This object contains setting entries that makes sense
/// at the user level. The defaults can also be overriden by /// at the user level. The defaults can also be overriden by
/// instance admins in the frontend_configuration endpoint or static config. /// instance admins in the frontend_configuration endpoint or static config.
export const instanceDefaultConfigDefinitions = { export const INSTANCE_DEFAULT_CONFIG_DEFINITIONS = {
expertLevel: { expertLevel: {
description: 'Used to track which settings to show and hide in settings modal', 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 type: 'number', // not a boolean so we could potentially make multiple levels of expert-ness
default: 0, default: 0,
}, },
@ -133,7 +139,8 @@ export const instanceDefaultConfigDefinitions = {
description: 'Hide shoutbox if present', description: 'Hide shoutbox if present',
default: false, default: false,
}, },
hideMutedPosts: { // bad name hideMutedPosts: {
// bad name
description: 'Hide posts of muted users entirely', description: 'Hide posts of muted users entirely',
default: false, default: false,
}, },
@ -205,7 +212,8 @@ export const instanceDefaultConfigDefinitions = {
default: false, default: false,
}, },
autohideFloatingPostButton: { autohideFloatingPostButton: {
description: 'Automatically hide mobile "new post" button when scrolling down', description:
'Automatically hide mobile "new post" button when scrolling down',
default: false, default: false,
}, },
stopGifs: { stopGifs: {
@ -400,7 +408,8 @@ export const instanceDefaultConfigDefinitions = {
default: false, default: false,
}, },
mentionLinkFadeDomain: { mentionLinkFadeDomain: {
description: 'Mute (fade) domain name in mention links if configured to show it', description:
'Mute (fade) domain name in mention links if configured to show it',
default: true, default: true,
}, },
mentionLinkShowYous: { mentionLinkShowYous: {
@ -448,7 +457,8 @@ export const instanceDefaultConfigDefinitions = {
default: false, default: false,
}, },
showExtraNotifications: { showExtraNotifications: {
description: 'Show extra notifications (chats, announcements etc) in notification panel', description:
'Show extra notifications (chats, announcements etc) in notification panel',
default: true, default: true,
}, },
showExtraNotificationsTip: { showExtraNotificationsTip: {
@ -507,41 +517,141 @@ export const instanceDefaultConfigDefinitions = {
description: 'Use 24h time format', description: 'Use 24h time format',
default: '24h', default: '24h',
}, },
themeChecksum: {
description: 'Checksum of theme used',
type: 'string',
required: false,
},
} }
export const instanceDefaultConfig = convertDefinitions(instanceDefaultConfigDefinitions) export const INSTANCE_DEFAULT_CONFIG = convertDefinitions(
INSTANCE_DEFAULT_CONFIG_DEFINITIONS,
)
export const defaultConfigLocal = { export const LOCAL_DEFAULT_CONFIG_DEFINITIONS = {
hideAttachments: false, // TODO these two used to be separate but since separation feature got broken it doesn't matter
hideAttachmentsInConv: false, hideAttachments: {
hideNsfw: true, description: 'Hide attachments in timeline',
useOneClickNsfw: false, default: false,
preloadImage: true, },
postContentType: 'text/plain', hideAttachmentsInConv: {
sidebarRight: false, description: 'Hide attachments in coversation',
sidebarColumnWidth: '25rem', default: false,
contentColumnWidth: '45rem', },
notifsColumnWidth: '25rem', hideNsfw: {
themeEditorMinWidth: '0rem', description: 'Hide nsfw posts',
emojiReactionsScale: 0.5, default: true,
textSize: '1rem', },
emojiSize: '2.2rem', useOneClickNsfw: {
navbarSize: '3.5rem', description: 'Open NSFW images directly in media modal',
panelHeaderSize: '3.2rem', default: false,
navbarColumnStretch: false, },
mentionLinkDisplay: 'short', preloadImage: {
alwaysUseJpeg: false, description: 'Preload images for NSFW',
imageCompression: true, default: true,
useStreamingApi: false, },
underlay: 'none', postContentType: {
fontInterface: undefined, description: 'Default post content type',
fontInput: undefined, default: 'text/plain',
fontPosts: undefined, },
fontMonospace: undefined, sidebarRight: {
themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions description: 'Reverse order of columns',
forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists default: false,
},
sidebarColumnWidth: {
description: 'Sidebar column width',
default: '25rem',
},
contentColumnWidth: {
description: 'Middle column width',
default: '45rem',
},
notifsColumnWidth: {
description: 'Notifications column width',
default: '25rem',
},
themeEditorMinWidth: {
description: 'Hack for theme editor on mobile',
default: '0rem',
},
emojiReactionsScale: {
description: 'Emoji reactions scale factor',
default: 0.5,
},
textSize: {
description: 'Font size',
default: '1rem',
},
emojiSize: {
description: 'Emoji size',
default: '2.2rem',
},
navbarSize: {
description: 'Navbar size',
default: '3.5rem',
},
panelHeaderSize: {
description: 'Panel header size',
default: '3.2rem',
},
navbarColumnStretch: {
description: 'Stretch navbar to match columns width',
default: false,
},
mentionLinkDisplay: {
description: 'How to display mention links',
default: 'short',
},
imageCompression: {
description: 'Image compression (WebP/JPEG)',
default: true,
},
alwaysUseJpeg: {
description: 'Compress images using JPEG only',
default: false,
},
useStreamingApi: {
description: 'Streaming API (WebSocket)',
default: false,
},
underlay: {
description: 'Underlay override',
default: 'none',
},
fontInterface: {
description: 'Interface font override',
type: 'string',
default: null,
},
fontInput: {
description: 'Input font override',
type: 'string',
default: null,
},
fontPosts: {
description: 'Post font override',
type: 'string',
default: null,
},
fontMonospace: {
description: 'Monospace font override',
type: 'string',
default: null,
},
themeDebug: {
description:
'Debug mode that uses computed backgrounds instead of real ones to debug contrast functions',
default: false,
},
forceThemeRecompilation: {
description: 'Flag that forces recompilation on boot even if cache exists',
default: false,
},
} }
export const LOCAL_DEFAULT_CONFIG = convertDefinitions(
LOCAL_DEFAULT_CONFIG_DEFINITIONS,
)
export const LOCAL_ONLY_KEYS = new Set(Object.keys(defaultConfigLocal)) export const LOCAL_ONLY_KEYS = new Set(Object.keys(LOCAL_DEFAULT_CONFIG))
export const makeUndefined = (c) => export const makeUndefined = (c) =>
Object.fromEntries(Object.keys(c).map((key) => [key, undefined])) Object.fromEntries(Object.keys(c).map((key) => [key, undefined]))
@ -550,8 +660,8 @@ export const makeUndefined = (c) =>
/// make sense to be overriden on a instance-wide level. /// make sense to be overriden on a instance-wide level.
export const defaultState = { export const defaultState = {
// Set these to undefined so it does not interfere with default settings check // Set these to undefined so it does not interfere with default settings check
...makeUndefined(instanceDefaultConfig), ...makeUndefined(INSTANCE_DEFAULT_CONFIG),
...makeUndefined(defaultConfigLocal), ...makeUndefined(LOCAL_DEFAULT_CONFIG),
// If there are any configurations that does not make sense to // If there are any configurations that does not make sense to
// have instance-wide default, put it here and explain why. // have instance-wide default, put it here and explain why.
@ -572,3 +682,53 @@ export const defaultState = {
palette: null, palette: null,
paletteCustomData: null, paletteCustomData: null,
} }
export const validateSetting = ({
value,
path,
definition,
throwError,
defaultState,
}) => {
if (value === undefined) return // only null is allowed as missing value
if (get(defaultState, path) === undefined) {
const string = `Unknown instance option ${path}, value: ${value}`
if (throwError) {
throw new Error(string)
} else {
console.error(string)
return value
}
}
let { required, type, default: defaultValue } = definition
if (type == null && defaultValue != null) {
type = typeof defaultValue
}
if (required && value == null) {
const string = `Value required for setting ${path} but was provided nullish; defaulting`
if (throwError) {
throw new Error(string)
} else {
console.error(string)
return defaultValue
}
}
if (value !== null && type != null && typeof value !== type) {
const string = `Invalid type for setting ${path}: expected type ${type}, got ${typeof value}, value ${value}; defaulting`
if (throwError) {
throw new Error(string)
} else {
console.error(string)
return defaultValue
}
}
return value
}

View file

@ -1,41 +1,146 @@
import { get, set } from 'lodash' import { get, set } from 'lodash'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { instanceDefaultProperties } from '../modules/config.js'
import { import {
defaultConfigLocal, convertDefinitions,
instanceDefaultConfig, INSTANCE_DEFAULT_CONFIG,
instanceIdentityDefault, INSTANCE_DEFAULT_CONFIG_DEFINITIONS,
INSTANCE_IDENTITY_DEFAULT,
INSTANCE_IDENTITY_DEFAULT_DEFINITIONS,
LOCAL_DEFAULT_CONFIG,
LOCAL_DEFAULT_CONFIG_DEFINITIONS,
validateSetting,
} from '../modules/default_config_state.js' } from '../modules/default_config_state.js'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { ensureFinalFallback } from 'src/i18n/languages.js'
const REMOTE_INTERACTION_URL = '/main/ostatus' const REMOTE_INTERACTION_URL = '/main/ostatus'
const defaultState = { const ROOT_STATE_DEFINITIONS = {
name: 'Pleroma FE', name: {
registrationOpen: true, type: 'string',
server: 'http://localhost:4040/', default: 'PleromaFE',
textlimit: 5000, },
privateMode: false, registrationOpen: {
federating: true, required: true,
federationPolicy: null, type: 'boolean',
themesIndex: null, },
stylesIndex: null, server: {
palettesIndex: null, description: 'Server URL',
themeData: null, // used for theme editor v2 required: true,
vapidPublicKey: null, type: 'string',
},
privateMode: {
description: 'Private instance?',
required: true,
type: 'boolean',
},
federating: {
description: 'Is federation enabled?',
required: true,
type: 'boolean',
},
federationPolicy: {
description: '????',
required: true,
type: 'object',
},
// Indexes of themes/styles/palettes
themesIndex: {
required: true,
type: 'object',
},
stylesIndex: {
required: true,
type: 'object',
},
palettesIndex: {
required: true,
type: 'object',
},
themeData: {
description: 'Used for theme v2 editor',
required: true,
type: 'object',
},
vapidPublicKey: {
description: 'Used for WebPush',
required: true,
type: 'string',
},
// Stuff from static/config.json // Stuff from static/config.json
loginMethod: 'password', loginMethod: {
disableUpdateNotification: false, description: 'Login method (token/password)',
default: 'password',
},
disableUpdateNotification: {
description: 'Disable update notification (pleroma-tan one)',
default: false,
},
knownDomains: {
description: 'List of known domains; used for domain list for domain mutes',
default: [],
},
// Moderation stuff
staffAccounts: {
description: 'List of staff accounts',
default: [],
},
accountActivationRequired: {
description:
'Account activation (by email or admin) required after registration',
required: true,
type: 'boolean',
},
accountApprovalRequired: {
description: 'Admin approval required after registration',
required: true,
type: 'boolean',
},
birthdayRequired: {
description: 'Require birthday entry when registering',
required: true,
type: 'boolean',
},
birthdayMinAge: {
description: 'Minimum age required for registration',
default: 0,
},
restrictedNicknames: {
description: 'List of nicknames that are not allowed',
default: [],
},
localBubbleInstances: {
description: "Akkoma's bubble feature, list of instances",
default: [],
}, // Akkoma
// Version Information
backendVersion: {
required: true,
type: 'string',
},
backendRepository: {
required: true,
type: 'string',
},
frontendVersion: {
required: true,
type: 'string',
},
}
const ROOT_STATE = convertDefinitions(ROOT_STATE_DEFINITIONS)
const DEFAULT_STATE = {
...ROOT_STATE,
// Instance-wide configurations that should not be changed by individual users // Instance-wide configurations that should not be changed by individual users
instanceIdentity: { instanceIdentity: {
...instanceIdentityDefault, ...INSTANCE_IDENTITY_DEFAULT,
}, },
limits: { limits: {
@ -44,6 +149,7 @@ const defaultState = {
backgroundlimit: null, backgroundlimit: null,
uploadlimit: null, uploadlimit: null,
fieldsLimits: null, fieldsLimits: null,
textLimit: null,
pollLimits: { pollLimits: {
max_options: 4, max_options: 4,
max_option_chars: 255, max_option_chars: 255,
@ -54,50 +160,53 @@ const defaultState = {
// Instance admins can override default settings for the whole instance // Instance admins can override default settings for the whole instance
prefsStorage: { prefsStorage: {
...instanceDefaultConfig, ...INSTANCE_DEFAULT_CONFIG,
...defaultConfigLocal, ...LOCAL_DEFAULT_CONFIG,
}, },
// Known domains list for user's domain-muting
knownDomains: [],
// Moderation stuff
staffAccounts: [],
accountActivationRequired: null,
accountApprovalRequired: null,
birthdayRequired: false,
birthdayMinAge: 0,
restrictedNicknames: [],
localBubbleInstances: [], // Akkoma
// Version Information
backendVersion: '',
backendRepository: '',
frontendVersion: '',
} }
console.log('===', ROOT_STATE_DEFINITIONS)
export const useInstanceStore = defineStore('instance', { export const useInstanceStore = defineStore('instance', {
state: () => ({ ...defaultState }), state: () => ({ ...DEFAULT_STATE }),
getters: { getters: {
instanceDefaultConfig(state) {
return instanceDefaultProperties
.map((key) => [key, state[key]])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
},
instanceDomain(state) { instanceDomain(state) {
return new URL(this.server).hostname return new URL(this.server).hostname
}, },
}, },
actions: { actions: {
set({ path, name, value }) { set({ path, value }) {
if (get(defaultState, path ?? name) === undefined) let definition
console.error( const pathArray = path.split('.')
`Unknown instance option ${path ?? name}, value: ${value}`, const subpath = pathArray[1] ?? pathArray[0]
)
set(this, path ?? name, value) if (path.startsWith('instanceIdentity.')) {
definition = INSTANCE_IDENTITY_DEFAULT_DEFINITIONS[subpath]
} else if (path.startsWith('prefsStorage.')) {
definition = {
...LOCAL_DEFAULT_CONFIG_DEFINITIONS,
...INSTANCE_DEFAULT_CONFIG_DEFINITIONS,
}[subpath]
} else if (path.startsWith('limits.')) {
definition =
path === 'limits.pollLimits' || path === 'limits.fieldsLimits'
? { required: true, type: 'object' }
: { required: true, type: 'number' }
} else {
const definitions = ROOT_STATE_DEFINITIONS
definition = definitions[subpath]
}
if ((path ?? name) === 'name') useInterfaceStore().setPageTitle() const finalValue = validateSetting({
path,
value,
definition,
throwError: true,
defaultState: DEFAULT_STATE,
})
set(this, path, finalValue)
if (path === 'name') useInterfaceStore().setPageTitle()
}, },
async getKnownDomains() { async getKnownDomains() {
try { try {

View file

@ -4,14 +4,17 @@ import { toRaw } from 'vue'
import { useInstanceStore } from 'src/stores/instance' import { useInstanceStore } from 'src/stores/instance'
import { defaultState as configDefaultState } from 'src/modules/default_config_state' import {
LOCAL_DEFAULT_CONFIG,
LOCAL_DEFAULT_CONFIG_DEFINITIONS
} from 'src/modules/default_config_state'
export const defaultState = { export const defaultState = {
prefsStorage: { prefsStorage: {
...configDefaultState, ...LOCAL_DEFAULT_CONFIG,
}, },
tempStorage: { tempStorage: {
...configDefaultState, ...LOCAL_DEFAULT_CONFIG,
}, },
} }
@ -21,7 +24,17 @@ export const useLocalConfigStore = defineStore('local_config', {
}, },
actions: { actions: {
set({ path, value }) { set({ path, value }) {
set(this.prefsStorage, path, value) const definition = LOCAL_DEFAULT_CONFIG_DEFINITIONS[path]
const finalValue = validateSetting({
path,
value,
definition,
throwError: false,
defaultState: LOCAL_DEFAULT_CONFIG,
})
set(this.prefsStorage, path, finalValue)
}, },
setTemporarily({ path, value }) { setTemporarily({ path, value }) {
set(this.tempStorage, path, value) set(this.tempStorage, path, value)

View file

@ -25,8 +25,10 @@ import { useLocalConfigStore } from 'src/stores/local_config.js'
import { storage } from 'src/lib/storage.js' import { storage } from 'src/lib/storage.js'
import { import {
defaultState as configDefaultState, defaultState as configDefaultState,
defaultConfigLocal, validateSetting,
instanceDefaultConfig, INSTANCE_DEFAULT_CONFIG,
INSTANCE_DEFAULT_CONFIG_DEFINITIONS,
LOCAL_DEFAULT_CONFIG,
LOCAL_ONLY_KEYS, LOCAL_ONLY_KEYS,
} from 'src/modules/default_config_state.js' } from 'src/modules/default_config_state.js'
import { oldDefaultConfigSync } from 'src/modules/old_default_config_state.js' import { oldDefaultConfigSync } from 'src/modules/old_default_config_state.js'
@ -488,7 +490,19 @@ export const useSyncConfigStore = defineStore('sync_config', {
`Calling set on depth > 3 (path: ${path}) is not allowed`, `Calling set on depth > 3 (path: ${path}) is not allowed`,
) )
} }
set(this.prefsStorage, path, value)
const definition = INSTANCE_DEFAULT_CONFIG_DEFINITIONS[path.split('.')[1]]
const finalValue = validateSetting({
path: path.split('.')[1],
value,
definition,
throwError: false,
defaultState: INSTANCE_DEFAULT_CONFIG,
})
set(this.prefsStorage, path, finalValue)
this.prefsStorage._journal = [ this.prefsStorage._journal = [
...this.prefsStorage._journal, ...this.prefsStorage._journal,
{ operation: 'set', path, args: [value], timestamp: Date.now() }, { operation: 'set', path, args: [value], timestamp: Date.now() },
@ -750,12 +764,12 @@ export const useSyncConfigStore = defineStore('sync_config', {
? (tempPrefs[k] ?? ? (tempPrefs[k] ??
localPrefs[k] ?? localPrefs[k] ??
instancePrefs[k] ?? instancePrefs[k] ??
defaultConfigLocal[k]) LOCAL_DEFAULT_CONFIG[k])
: (tempPrefs[k] ?? : (tempPrefs[k] ??
localPrefs[k] ?? localPrefs[k] ??
value ?? value ??
instancePrefs[k] ?? instancePrefs[k] ??
instanceDefaultConfig[k]), INSTANCE_DEFAULT_CONFIG[k]),
]), ]),
) )
return result return result
@ -766,8 +780,8 @@ export const useSyncConfigStore = defineStore('sync_config', {
Object.entries(state.prefsStorage.simple).map(([k, value]) => [ Object.entries(state.prefsStorage.simple).map(([k, value]) => [
k, k,
LOCAL_ONLY_KEYS.has(k) LOCAL_ONLY_KEYS.has(k)
? (instancePrefs[k] ?? defaultConfigLocal[k]) ? (instancePrefs[k] ?? LOCAL_DEFAULT_CONFIG[k])
: (instancePrefs[k] ?? instanceDefaultConfig[k]), : (instancePrefs[k] ?? INSTANCE_DEFAULT_CONFIG[k]),
]), ]),
) )
return result return result

View file

@ -5,7 +5,7 @@ import 'virtual:pleroma-fe/service_worker_env'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
import { storage } from 'src/lib/storage.js' import { storage } from 'src/lib/storage.js'
import { instanceDefaultConfig } from 'src/modules/default_config_state.js' import { INSTANCE_DEFAULT_CONFIG } from 'src/modules/default_config_state.js'
import { parseNotification } from 'src/services/entity_normalizer/entity_normalizer.service.js' import { parseNotification } from 'src/services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from 'src/services/notification_utils/notification_utils.js' import { prepareNotificationObject } from 'src/services/notification_utils/notification_utils.js'
import { cacheKey, emojiCacheKey, shouldCache } from 'src/services/sw/sw.js' import { cacheKey, emojiCacheKey, shouldCache } from 'src/services/sw/sw.js'
@ -40,7 +40,7 @@ const setSettings = async () => {
i18n.locale = locale i18n.locale = locale
const notificationsNativeArray = Object.entries( const notificationsNativeArray = Object.entries(
piniaState.prefsStorage.simple.notificationNative || piniaState.prefsStorage.simple.notificationNative ||
instanceDefaultConfig.notificationNative, INSTANCE_DEFAULT_CONFIG.notificationNative,
) )
state.webPushAlwaysShowNotifications = state.webPushAlwaysShowNotifications =
piniaState.prefsStorage.simple.webPushAlwaysShowNotifications piniaState.prefsStorage.simple.webPushAlwaysShowNotifications