diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js index d1f3390cc..d7c5aa225 100644 --- a/src/components/font_control/font_control.js +++ b/src/components/font_control/font_control.js @@ -21,14 +21,7 @@ export default { Popover, LocalSettingIndicator, }, - props: [ - 'name', - 'label', - 'modelValue', - 'fallback', - 'options', - 'no-inherit', - ], + props: ['name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'], mounted() { useInterfaceStore().queryLocalFonts() }, diff --git a/src/components/settings_modal/helpers/list_setting.js b/src/components/settings_modal/helpers/list_setting.js index c1d504cd5..232ea123f 100644 --- a/src/components/settings_modal/helpers/list_setting.js +++ b/src/components/settings_modal/helpers/list_setting.js @@ -1,6 +1,8 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import Setting from './setting.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' + export default { ...Setting, data() { @@ -43,7 +45,7 @@ export default { if (this.forceNew) return true if (!this.allowNew) return false - const isExpert = this.$store.state.config.expertLevel > 0 + const isExpert = useSyncConfigStore().mergedConfig.expertLevel > 0 const hasBuiltins = this.builtinEntries.length > 0 if (hasBuiltins) { diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index 25c508bef..a99067f36 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -9,6 +9,10 @@ import PanelLoading from 'src/components/panel_loading/panel_loading.vue' import Popover from '../popover/popover.vue' import { useInterfaceStore } from 'src/stores/interface.js' +import { + LOCAL_ONLY_KEYS, + useLocalConfigStore, +} from 'src/stores/local_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { @@ -137,7 +141,34 @@ const SettingsModal = { }, onImport(data) { if (data) { - this.$store.dispatch('loadSettings', data) + Object.entries(data).forEach(([path, value]) => { + if (LOCAL_ONLY_KEYS.has(path)) { + useLocalConfigStore().set({ path, value }) + } else { + if (path.startsWith('muteFilters')) { + Object.keys( + useSyncConfigStore().mergedConfig.muteFilters, + ).forEach((key) => { + useSyncConfigStore().unsetPreference({ + path: `simple.${path}.${key}`, + }) + }) + + Object.entries(value).forEach(([key, filter]) => { + useSyncConfigStore().setPreference({ + path: `simple.${path}.${key}`, + value: filter, + }) + }) + } else { + useSyncConfigStore().setPreference({ + path: `simple.${path}`, + value, + }) + } + } + }) + useSyncConfigStore().pushSyncConfig() } }, restore() { @@ -150,7 +181,7 @@ const SettingsModal = { this.dataThemeExporter.exportData() }, generateExport(theme = false) { - const { config } = this.$store.state + const config = useSyncConfigStore().mergedConfigWithoutDefaults let sample = config if (!theme) { const ignoreList = new Set([ @@ -159,7 +190,9 @@ const SettingsModal = { 'colors', ]) sample = Object.fromEntries( - Object.entries(sample).filter(([key]) => !ignoreList.has(key)), + Object.entries(sample).filter( + ([key, value]) => !ignoreList.has(key) && value !== undefined, + ), ) } const clone = cloneDeep(sample) diff --git a/src/components/settings_modal/settings_modal_user_content.js b/src/components/settings_modal/settings_modal_user_content.js index ddee812ca..6c33b4fa6 100644 --- a/src/components/settings_modal/settings_modal_user_content.js +++ b/src/components/settings_modal/settings_modal_user_content.js @@ -16,6 +16,7 @@ import SecurityTab from './tabs/security_tab/security_tab.vue' import StyleTab from './tabs/style_tab/style_tab.vue' import { useInterfaceStore } from 'src/stores/interface.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -83,7 +84,7 @@ const SettingsModalContent = { return useInterfaceStore().settingsModalState === 'visible' }, expertLevel() { - return this.$store.state.config.expertLevel + return useSyncConfigStore().mergedConfig.expertLevel }, }, data() { diff --git a/src/components/settings_modal/tabs/clutter_tab.js b/src/components/settings_modal/tabs/clutter_tab.js index 510425e2d..85749ab3f 100644 --- a/src/components/settings_modal/tabs/clutter_tab.js +++ b/src/components/settings_modal/tabs/clutter_tab.js @@ -38,55 +38,6 @@ const ClutterTab = { Object.entries(store.prefsStorage.simple.muteFilters), muteFiltersObject: (store) => store.prefsStorage.simple.muteFilters, }), - onMuteDefaultActionLv1: { - get() { - const value = this.$store.state.config.onMuteDefaultAction - if (value === 'ask' || value === 'forever') { - return value - } else { - return 'temporarily' - } - }, - set(value) { - let realValue = value - if (value !== 'ask' && value !== 'forever') { - realValue = '14d' - } - useSyncConfigStore().setSimplePrefAndSave({ - path: 'onMuteDefaultAction', - value: realValue, - }) - }, - }, - onBlockDefaultActionLv1: { - get() { - const value = this.$store.state.config.onBlockDefaultAction - if (value === 'ask' || value === 'forever') { - return value - } else { - return 'temporarily' - } - }, - set(value) { - let realValue = value - if (value !== 'ask' && value !== 'forever') { - realValue = '14d' - } - useSyncConfigStore().setSimplePrefAndSave({ - path: 'onBlockDefaultAction', - value: realValue, - }) - }, - }, - muteFiltersDraft() { - return Object.entries(this.muteFiltersDraftObject) - }, - muteFiltersExpired() { - const now = Date.now() - return Object.entries(this.muteFiltersDraftObject).filter( - ([, { expires }]) => expires != null && expires <= now, - ) - }, }, methods: { ...mapActions(useSyncConfigStore, [ diff --git a/src/components/settings_modal/tabs/clutter_tab.vue b/src/components/settings_modal/tabs/clutter_tab.vue index 8ae416ff8..533cd85c6 100644 --- a/src/components/settings_modal/tabs/clutter_tab.vue +++ b/src/components/settings_modal/tabs/clutter_tab.vue @@ -83,9 +83,7 @@
  • - + {{ $t('settings.hide_shoutbox') }}
  • diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index facc6073f..3598de757 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -93,14 +93,12 @@ const FilteringTab = { computed: { ...SharedComputedObject(), ...mapState(useSyncConfigStore, { - muteFilters: (store) => - Object.entries(store.prefsStorage.simple.muteFilters), muteFiltersObject: (store) => store.prefsStorage.simple.muteFilters, }), ...mapState(useInstanceCapabilitiesStore, ['blockExpiration']), onMuteDefaultActionLv1: { get() { - const value = this.$store.state.config.onMuteDefaultAction + const value = useSyncConfigStore().mergedConfig.onMuteDefaultAction if (value === 'ask' || value === 'forever') { return value } else { @@ -120,7 +118,7 @@ const FilteringTab = { }, onBlockDefaultActionLv1: { get() { - const value = this.$store.state.config.onBlockDefaultAction + const value = useSyncConfigStore().mergedConfig.onBlockDefaultAction if (value === 'ask' || value === 'forever') { return value } else { @@ -151,7 +149,7 @@ const FilteringTab = { methods: { ...mapActions(useSyncConfigStore, [ 'setPreference', - 'setPrefAndSave', + 'setSimplePrefAndSave', 'unsetPreference', 'unsetPrefAndSave', 'pushSyncConfig', @@ -260,6 +258,12 @@ const FilteringTab = { replyVisibility() { this.$store.dispatch('queueFlushAll') }, + muteFiltersObject() { + console.log('UPDATE') + this.muteFiltersDraftObject = cloneDeep( + useSyncConfigStore().prefsStorage.simple.muteFilters, + ) + }, }, } diff --git a/src/components/settings_modal/tabs/posts_tab.vue b/src/components/settings_modal/tabs/posts_tab.vue index d14475fc3..444f96392 100644 --- a/src/components/settings_modal/tabs/posts_tab.vue +++ b/src/components/settings_modal/tabs/posts_tab.vue @@ -101,6 +101,7 @@ {{ $t('settings.mention_link_display') }} @@ -171,7 +172,10 @@
  • - + {{ $t('settings.nsfw_clickthrough') }}
      @@ -179,6 +183,7 @@ {{ $t('settings.preload_images') }} @@ -188,6 +193,7 @@ {{ $t('settings.use_one_click_nsfw') }} diff --git a/src/i18n/en.json b/src/i18n/en.json index 70c099932..0165af240 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -381,6 +381,7 @@ "select_all": "Select all" }, "settings": { + "invalid_settings_imported": "Error importing settings", "add_language": "Add fallback language", "remove_language": "Remove", "primary_language": "Primary language:", diff --git a/src/main.js b/src/main.js index a4269b4a7..ceb8c0878 100644 --- a/src/main.js +++ b/src/main.js @@ -41,7 +41,7 @@ const i18n = createI18n({ messages.setLanguage(i18n.global, currentLocale) const persistedStateOptions = { - paths: ['syncConfig.cache', 'config', 'users.lastLoginName', 'oauth'], + paths: ['users.lastLoginName', 'oauth'], } ;(async () => { diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index fbfaf08a3..a391e9b21 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -49,8 +49,6 @@ export const instanceDefaultConfig = { hideScrobbles: false, hideScrobblesAfter: '2d', maxThumbnails: 16, - hideNsfw: true, - preloadImage: true, loopVideo: true, loopVideoSilentOnly: true, /// This is not the streaming API configuration, but rather an option @@ -121,7 +119,6 @@ export const instanceDefaultConfig = { modalMobileCenter: false, playVideosInModal: false, - useOneClickNsfw: false, useContainFit: true, disableStickyHeaders: false, showScrollbars: false, @@ -165,6 +162,9 @@ export const instanceDefaultConfig = { export const defaultConfigLocal = { hideAttachments: false, hideAttachmentsInConv: false, + hideNsfw: true, + useOneClickNsfw: false, + preloadImage: true, postContentType: 'text/plain', sidebarRight: false, sidebarColumnWidth: '25rem', @@ -191,9 +191,12 @@ export const makeUndefined = (c) => export const defaultState = { // Set these to undefined so it does not interfere with default settings check ...makeUndefined(instanceDefaultConfig), + ...makeUndefined(defaultConfigLocal), + // If there are any configurations that does not make sense to + // have instance-wide default, put it here and explain why. - // Special processing - // Theme stuff + // # Special processing + // ## Theme stuff theme: undefined, // Very old theme store, stores preset name, still in use // V1 @@ -220,15 +223,4 @@ export const defaultState = { monospace: undefined, }, }, - - // Special handling: These fields are not of a primitive type, and - // might cause problems with current code because it specifically checks - // them in state.config (not getters.mergedConfig). - - // Specifically, muteWords is now deprecated in favour of a server-side configuration. - muteWords: [], - highlight: {}, - - // If there are any configurations that does not make sense to - // have instance-wide default, put it here and explain why. } diff --git a/src/modules/old_default_config_state.js b/src/modules/old_default_config_state.js index e53cafe29..dc15e1c24 100644 --- a/src/modules/old_default_config_state.js +++ b/src/modules/old_default_config_state.js @@ -18,8 +18,6 @@ export const defaultConfigSync = { hideScrobbles: false, hideScrobblesAfter: '2d', maxThumbnails: 16, - hideNsfw: true, - preloadImage: true, loopVideo: true, loopVideoSilentOnly: true, /// This is not the streaming API configuration, but rather an option @@ -93,7 +91,6 @@ export const defaultConfigSync = { modalMobileCenter: false, playVideosInModal: false, - useOneClickNsfw: false, useContainFit: true, disableStickyHeaders: false, showScrollbars: false, @@ -169,6 +166,9 @@ export const defaultConfigSync = { export const defaultConfigLocal = { hideAttachments: false, hideAttachmentsInConv: false, + hideNsfw: true, + useOneClickNsfw: false, + preloadImage: true, sidebarColumnWidth: '25rem', contentColumnWidth: '45rem', notifsColumnWidth: '25rem', @@ -182,6 +182,4 @@ export const defaultConfigLocal = { mentionLinkDisplay: 'short', imageCompression: true, alwaysUseJpeg: false, - imageCompression: true, - alwaysUseJpeg: false, } diff --git a/src/stores/local_config.js b/src/stores/local_config.js index 117be393d..3611b559e 100644 --- a/src/stores/local_config.js +++ b/src/stores/local_config.js @@ -6,6 +6,8 @@ import { useInstanceStore } from 'src/stores/instance' import { defaultState as configDefaultState } from 'src/modules/default_config_state' +export const LOCAL_ONLY_KEYS = new Set(Object.keys(configDefaultState)) + export const defaultState = { prefsStorage: { ...configDefaultState, @@ -32,7 +34,7 @@ export const useLocalConfigStore = defineStore('local_config', { unset({ path, value }) { set(this.prefsStorage, path, undefined) }, - clearSyncConfig() { + clearLocalConfig() { const blankState = { ...cloneDeep(defaultState) } Object.keys(this).forEach((k) => { this.prefsStorage[k] = blankState[k] diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index e43356e8e..996f32979 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -18,14 +18,21 @@ import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' import { useInstanceStore } from 'src/stores/instance.js' -import { useLocalConfigStore } from 'src/stores/local_config.js' +import { + LOCAL_ONLY_KEYS, + useLocalConfigStore, +} from 'src/stores/local_config.js' import { storage } from 'src/lib/storage.js' -import { defaultState as configDefaultState } from 'src/modules/default_config_state.js' +import { + defaultState as configDefaultState, + defaultConfigLocal, + instanceDefaultConfig, +} from 'src/modules/default_config_state.js' import { defaultConfigSync } from 'src/modules/old_default_config_state.js' export const VERSION = 2 -export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically +export const NEW_USER_DATE = new Date('2026-03-16') // date of writing this, basically export const COMMAND_TRIM_FLAGS = 1000 export const COMMAND_TRIM_FLAGS_AND_RESET = 1001 @@ -49,6 +56,7 @@ export const defaultState = { dontShowUpdateNotifs: false, collapseNav: false, muteFilters: {}, + ...configDefaultState, }, collections: { @@ -615,14 +623,14 @@ export const useSyncConfigStore = defineStore('sync_config', { let dirty = false console.debug('Migrating from old config') - const vuexState = await storage.getItem('vuex-lz') - const { config } = vuexState + const vuexState = (await storage.getItem('vuex-lz')) ?? {} + vuexState.config = vuexState.config ?? {} - const migratedEntries = new Set(config._syncMigration ?? []) + const migratedEntries = new Set(vuexState.config._syncMigration ?? []) console.debug(`Already migrated Values: ${[...migratedEntries].join()}`) Object.entries(defaultConfigSync).forEach(([key, value]) => { - const oldValue = config[key] + const oldValue = vuexState.config[key] const defaultValue = value const present = oldValue !== undefined @@ -630,12 +638,13 @@ export const useSyncConfigStore = defineStore('sync_config', { const different = !isEqual(oldValue, defaultValue) if (present && !migrated && different) { - console.debug(`Migrating config ${key}: ${oldValue}`,) + console.debug(`Migrating config ${key}: ${oldValue}`) this.setPreference({ path: `simple.${key}`, oldValue }) migratedEntries.add(key) needUpload = true } }) + vuexState.config._syncMigration = [...migratedEntries] storage.setItem('vuex-lz', vuexState) @@ -716,9 +725,28 @@ export const useSyncConfigStore = defineStore('sync_config', { const localPrefs = useLocalConfigStore().prefsStorage const tempPrefs = useLocalConfigStore().tempStorage const result = Object.fromEntries( - Object.entries(state.prefsStorage.simple).map(([k, v]) => [ + Object.entries(state.prefsStorage.simple).map(([k, value]) => [ k, - tempPrefs[k] ?? localPrefs[k] ?? v ?? instancePrefs[k], + LOCAL_ONLY_KEYS.has(k) + ? (tempPrefs[k] ?? + localPrefs[k] ?? + instancePrefs[k] ?? + defaultConfigLocal[k]) + : (tempPrefs[k] ?? + localPrefs[k] ?? + value ?? + instancePrefs[k] ?? + instanceDefaultConfig[k]), + ]), + ) + return result + }, + mergedConfigWithoutDefaults: (state) => { + const localPrefs = useLocalConfigStore().prefsStorage + const result = Object.fromEntries( + Object.entries(state.prefsStorage.simple).map(([k, value]) => [ + k, + LOCAL_ONLY_KEYS.has(k) ? localPrefs[k] : value, ]), ) return result diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index c8868921a..ae2bd4d4c 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -278,8 +278,10 @@ export const useUserHighlightStore = defineStore('user_highlight', { const userNew = userData.created_at > NEW_USER_DATE let dirty = false - const vuexState = await storage.getItem('vuex-lz') - const { highlight } = vuexState.config + const vuexState = (await storage.getItem('vuex-lz')) ?? {} + vuexState.config = vuexState.config ?? {} + const highlight = vuexState.config.highlight ?? {} + Object.entries(highlight).forEach(([user, value]) => { if ((highlight[user]._migrated || 0) < 1) { dirty = true diff --git a/src/sw.js b/src/sw.js index e3225c91d..bfe35d3c5 100644 --- a/src/sw.js +++ b/src/sw.js @@ -34,14 +34,14 @@ function getWindowClients() { } const setSettings = async () => { - const vuexState = await storage.getItem('vuex-lz') - const locale = vuexState.config.interfaceLanguage || 'en' + const piniaState = await storage.getItem('pinia-local-sync_config') + const locale = piniaState.prefsStorage.simple.interfaceLanguage || 'en' i18n.locale = locale const notificationsNativeArray = Object.entries( - vuexState.config.notificationNative, + piniaState.prefsStorage.simple.notificationNative, ) state.webPushAlwaysShowNotifications = - vuexState.config.webPushAlwaysShowNotifications + piniaState.prefsStorage.simple.webPushAlwaysShowNotifications state.allowedNotificationTypes = new Set( notificationsNativeArray