diff --git a/src/boot/after_store.js b/src/boot/after_store.js index a345e2ae5..d16852bf8 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -42,6 +42,7 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock' import { INSTANCE_DEFAULT_CONFIG_DEFINITIONS, INSTANCE_IDENTITY_DEFAULT_DEFINITIONS, + INSTANCE_IDENTIY_EXTERNAL, } from 'src/modules/default_config_state.js' let staticInitialResults = null @@ -170,12 +171,14 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { config = Object.assign({}, staticConfig, apiConfig) } - Object.keys(INSTANCE_IDENTITY_DEFAULT_DEFINITIONS).forEach((source) => + Object.keys(INSTANCE_IDENTITY_DEFAULT_DEFINITIONS).forEach((source) => { + if (source === 'name') return + if (INSTANCE_IDENTIY_EXTERNAL.has(source)) return useInstanceStore().set({ value: config[source], path: `instanceIdentity.${source}`, - }), - ) + }) + }) Object.keys(INSTANCE_DEFAULT_CONFIG_DEFINITIONS).forEach((source) => useInstanceStore().set({ @@ -277,7 +280,7 @@ const getNodeInfo = async ({ store }) => { const metadata = data.metadata const features = metadata.features useInstanceStore().set({ - path: 'name', + path: 'instanceIdentity.name', value: metadata.nodeName, }) useInstanceStore().set({ @@ -527,6 +530,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { useInterfaceStore().setLayoutWidth(windowWidth()) useInterfaceStore().setLayoutHeight(windowHeight()) + window.syncConfig = useSyncConfigStore() window.mergedConfig = useMergedConfigStore() window.localConfig = useLocalConfigStore() diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index 4ca2857b2..5c157e068 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -13,7 +13,12 @@ import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { LOCAL_ONLY_KEYS } from 'src/modules/default_config_state.js' +import { + LOCAL_ONLY_KEYS, + ROOT_CONFIG, + ROOT_CONFIG_DEFINITIONS, + validateSetting, +} from 'src/modules/default_config_state.js' import { newExporter, newImporter, @@ -138,37 +143,52 @@ const SettingsModal = { }) } }, - onImport(data) { - if (data) { - Object.entries(data).forEach(([path, value]) => { - if (LOCAL_ONLY_KEYS.has(path)) { - useLocalConfigStore().set({ path, value }) - } else { - if (path.startsWith('muteFilters')) { - Object.keys( - useMergedConfigStore().mergedConfig.muteFilters, - ).forEach((key) => { - useSyncConfigStore().unsetPreference({ - path: `simple.${path}.${key}`, - }) - }) + onImport(input) { + if (!input) return + const { _pleroma_settings_version, ...data } = input - Object.entries(value).forEach(([key, filter]) => { - useSyncConfigStore().setPreference({ - path: `simple.${path}.${key}`, - value: filter, - }) + Object.entries(data).forEach(([path, value]) => { + const definition = ROOT_CONFIG_DEFINITIONS[path] + + const finalValue = validateSetting({ + path, + value, + definition, + throwError: false, + defaultState: ROOT_CONFIG, + }) + + if (finalValue === undefined) return + + if (LOCAL_ONLY_KEYS.has(path)) { + useLocalConfigStore().set({ path, value: finalValue }) + } else { + if (path.startsWith('muteFilters')) { + Object.keys( + useMergedConfigStore().mergedConfig.muteFilters, + ).forEach((key) => { + useSyncConfigStore().unsetPreference({ + path: `simple.${path}.${key}`, }) - } else { + }) + + Object.entries(value).forEach(([key, filter]) => { + useSyncConfigStore().setPreference({ + path: `simple.${path}.${key}`, + value: filter, + }) + }) + } else { + if (finalValue !== undefined) { useSyncConfigStore().setPreference({ path: `simple.${path}`, - value, + value: finalValue, }) } } - }) - useSyncConfigStore().pushSyncConfig() - } + } + }) + useSyncConfigStore().pushSyncConfig() }, restore() { this.dataImporter.importData() @@ -184,10 +204,17 @@ const SettingsModal = { let sample = config if (!theme) { const ignoreList = new Set([ + 'theme', 'customTheme', 'customThemeSource', 'colors', + 'style', + 'styleCustomData', + 'palette', + 'paletteCustomData', + 'themeChecksum', ]) + sample = Object.fromEntries( Object.entries(sample).filter( ([key, value]) => !ignoreList.has(key) && value !== undefined, diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index b5958adb6..ff43e4dee 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -112,10 +112,19 @@ export const INSTANCE_IDENTITY_DEFAULT_DEFINITIONS = { type: 'string', required: false, }, + name: { + description: 'Instance Name', + type: 'string', + required: false, + }, } export const INSTANCE_IDENTITY_DEFAULT = convertDefinitions( INSTANCE_IDENTITY_DEFAULT_DEFINITIONS, ) +export const INSTANCE_IDENTIY_EXTERNAL = new Set([ + 'tos', + 'instanceSpecificPanelContent', +]) /// This object contains setting entries that makes sense /// at the user level. The defaults can also be overriden by @@ -522,6 +531,18 @@ export const INSTANCE_DEFAULT_CONFIG_DEFINITIONS = { type: 'string', required: false, }, + theme3hacks: { + description: 'Theme 3 hacks (need separation)', + type: 'object', + required: false, + default: {}, + }, + highlights: { + description: 'User highlights', + type: 'object', + required: false, + default: {}, + }, } export const INSTANCE_DEFAULT_CONFIG = convertDefinitions( INSTANCE_DEFAULT_CONFIG_DEFINITIONS, @@ -663,6 +684,11 @@ export const SYNC_DEFAULT_CONFIG_DEFINITIONS = { description: 'Collapse navigation panel to header only', default: false, }, + muteFilters: { + description: 'Object containing mute filters', + type: 'object', + default: {}, + } } export const SYNC_DEFAULT_CONFIG = convertDefinitions( SYNC_DEFAULT_CONFIG_DEFINITIONS, @@ -731,14 +757,27 @@ export const ROOT_CONFIG_DEFINITIONS = { export const validateSetting = ({ value, - path, + path: fullPath, definition, throwError, defaultState, + validateObjects = true, }) => { - if (value === undefined) return // only null is allowed as missing value - if (get(defaultState, path) === undefined) { - const string = `Unknown instance option ${path}, value: ${value}` + const path = fullPath.replace(/^simple./, '') + if (validateObjects && definition.type === 'object' && path.split('.').length <= 1) { + console.error(`attempt to set object ${fullPath} instead of its children. ignoring.`) + return undefined + } + + if (path.includes('muteFilters')) { + console.log('##', path, value, definition) + console.log(value) + console.log(path) + console.log('====') + } + if (value === undefined) return undefined // only null is allowed as missing value + if (get(defaultState, path.split('.')[0]) === undefined) { + const string = `Unknown option ${fullPath}, value: ${value}` if (throwError) { throw new Error(string) diff --git a/src/stores/instance.js b/src/stores/instance.js index 2923f3ad8..54b3cf43c 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -201,6 +201,7 @@ export const useInstanceStore = defineStore('instance', { definition, throwError: true, defaultState: DEFAULT_STATE, + validateObjects: false, }) set(this, path, finalValue) diff --git a/src/stores/merged_config.js b/src/stores/merged_config.js index c4f27082c..334db6c4f 100644 --- a/src/stores/merged_config.js +++ b/src/stores/merged_config.js @@ -5,18 +5,10 @@ import { useLocalConfigStore } from 'src/stores/local_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { - INSTANCE_DEFAULT_CONFIG, - LOCAL_DEFAULT_CONFIG, LOCAL_ONLY_KEYS, - THEME_CONFIG, + ROOT_CONFIG, } from 'src/modules/default_config_state.js' -const ROOT_CONFIG = { - ...INSTANCE_DEFAULT_CONFIG, - ...LOCAL_DEFAULT_CONFIG, - ...THEME_CONFIG, -} - export const useMergedConfigStore = defineStore('merged_config', { getters: { mergedConfig: () => { @@ -50,16 +42,15 @@ export const useMergedConfigStore = defineStore('merged_config', { return result }, mergedConfigWithoutDefaults: () => { - const instancePrefs = useInstanceStore().prefsStorage const tempPrefs = useLocalConfigStore().tempStorage const localPrefs = useLocalConfigStore().prefsStorage const syncPrefs = useSyncConfigStore().prefsStorage const getValue = (k) => - tempPrefs[k] ?? localPrefs[k] ?? syncPrefs.simple[k] ?? instancePrefs[k] + tempPrefs[k] ?? localPrefs[k] ?? syncPrefs.simple[k] const result = Object.fromEntries( - Object.keys(ROOT_CONFIG).map(([k, value]) => [k, getValue(k)]), + Object.keys(ROOT_CONFIG).map((k) => [k, getValue(k)]), ) return result }, diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index 7a5b1f4e1..bad0a1515 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -295,12 +295,14 @@ export const _mergePrefs = (recent, stale) => { const entry = path.split('.')[1] if (operation === 'unset') return ROOT_CONFIG[entry] !== undefined + if (operation !== 'set') return true + const definition = path.startsWith('simple.muteFilters') ? { default: {} } : ROOT_CONFIG_DEFINITIONS[entry] const finalValue = validateSetting({ - path: entry, + path, value: args[0], definition, throwError: false, @@ -507,12 +509,14 @@ export const useSyncConfigStore = defineStore('sync_config', { ) } + if (path.startsWith('collections.')) return value + const definition = path.startsWith('simple.muteFilters') ? { default: {} } : ROOT_CONFIG_DEFINITIONS[path.split('.')[1]] const finalValue = validateSetting({ - path: path.split('.')[1], + path, value, definition, throwError: false, @@ -773,21 +777,19 @@ export const useSyncConfigStore = defineStore('sync_config', { afterLoad(state) { console.debug('Validating persisted state of SyncConfig') const newState = { ...state } - const newEntries = Object.entries(newState.prefsStorage.simple).map( + const newEntries = Object.entries(ROOT_CONFIG).map( ([path, value]) => { - if (path === 'muteFilters') { - return value - } const definition = ROOT_CONFIG_DEFINITIONS[path] const finalValue = validateSetting({ path, - value, + value: newState.prefsStorage.simple[path], definition, throwError: false, + validateObjects: false, defaultState: ROOT_CONFIG, }) - return finalValue === undefined ? undefined : [path, finalValue] + return finalValue === undefined ? definition.default : [path, finalValue] }, ) newState.prefsStorage.simple = Object.fromEntries( diff --git a/test/unit/specs/stores/sync_config.spec.js b/test/unit/specs/stores/sync_config.spec.js index 9a582a193..83324f62b 100644 --- a/test/unit/specs/stores/sync_config.spec.js +++ b/test/unit/specs/stores/sync_config.spec.js @@ -241,7 +241,9 @@ describe('The SyncConfig store', () => { store.setPreference({ path: 'simple.fontInput.family', value: 'test' }) store.unsetPreference({ path: 'simple.fontInput.family' }) store.updateCache(store, { username: 'test' }) - expect(store.prefsStorage.simple.fontInput).to.not.have.property('family') + expect(store.prefsStorage.simple.fontInput).to.not.have.property( + 'family', + ) expect(store.prefsStorage._journal.length).to.eql(1) }) @@ -395,7 +397,12 @@ describe('The SyncConfig store', () => { { simple: { theme: '1', style: '0', hideISP: true }, _journal: [ - { path: 'simple.style', operation: 'set', args: ['0'], timestamp: 2 }, + { + path: 'simple.style', + operation: 'set', + args: ['0'], + timestamp: 2, + }, { path: 'simple.hideISP', operation: 'set', @@ -408,17 +415,42 @@ describe('The SyncConfig store', () => { { simple: { theme: '1', style: '1', hideISP: false }, _journal: [ - { path: 'simple.theme', operation: 'set', args: ['1'], timestamp: 1 }, - { path: 'simple.style', operation: 'set', args: ['1'], timestamp: 3 }, + { + path: 'simple.theme', + operation: 'set', + args: ['1'], + timestamp: 1, + }, + { + path: 'simple.style', + operation: 'set', + args: ['1'], + timestamp: 3, + }, ], }, ), ).to.eql({ simple: { theme: '1', style: '1', hideISP: true }, _journal: [ - { path: 'simple.theme', operation: 'set', args: ['1'], timestamp: 1 }, - { path: 'simple.style', operation: 'set', args: ['1'], timestamp: 3 }, - { path: 'simple.hideISP', operation: 'set', args: [true], timestamp: 4 }, + { + path: 'simple.theme', + operation: 'set', + args: ['1'], + timestamp: 1, + }, + { + path: 'simple.style', + operation: 'set', + args: ['1'], + timestamp: 3, + }, + { + path: 'simple.hideISP', + operation: 'set', + args: [true], + timestamp: 4, + }, ], }) }) @@ -430,7 +462,12 @@ describe('The SyncConfig store', () => { { simple: { theme: '1', style: '0', hideISP: false }, _journal: [ - { path: 'simple.style', operation: 'set', args: ['0'], timestamp: 2 }, + { + path: 'simple.style', + operation: 'set', + args: ['0'], + timestamp: 2, + }, { path: 'simple.hideISP', operation: 'set', @@ -443,18 +480,42 @@ describe('The SyncConfig store', () => { { simple: { theme: '0', style: '0', hideISP: true }, _journal: [ - { path: 'simple.theme', operation: 'set', args: ['0'], timestamp: 1 }, - { path: 'simple.style', operation: 'set', args: ['0'], timestamp: 3 }, + { + path: 'simple.theme', + operation: 'set', + args: ['0'], + timestamp: 1, + }, + { + path: 'simple.style', + operation: 'set', + args: ['0'], + timestamp: 3, + }, ], }, ), ).to.eql({ - simple: { a: 0, b: 0, c: false }, simple: { theme: '0', style: '0', hideISP: false }, _journal: [ - { path: 'simple.theme', operation: 'set', args: ['0'], timestamp: 1 }, - { path: 'simple.style', operation: 'set', args: ['0'], timestamp: 3 }, - { path: 'simple.hideISP', operation: 'set', args: [false], timestamp: 4 }, + { + path: 'simple.theme', + operation: 'set', + args: ['0'], + timestamp: 1, + }, + { + path: 'simple.style', + operation: 'set', + args: ['0'], + timestamp: 3, + }, + { + path: 'simple.hideISP', + operation: 'set', + args: [false], + timestamp: 4, + }, ], }) }) @@ -490,7 +551,12 @@ describe('The SyncConfig store', () => { ).to.eql({ simple: { theme: 'bar' }, _journal: [ - { path: 'simple.theme', operation: 'set', args: ['bar'], timestamp: 4 }, + { + path: 'simple.theme', + operation: 'set', + args: ['bar'], + timestamp: 4, + }, ], }) })