Merge branch 'setttingssync' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2026-03-16 17:58:52 +02:00
commit f6fd5ed8c4
16 changed files with 121 additions and 110 deletions

View file

@ -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()
},

View file

@ -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) {

View file

@ -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)

View file

@ -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() {

View file

@ -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, [

View file

@ -83,9 +83,7 @@
</BooleanSetting>
</li>
<li v-if="shoutAvailable">
<BooleanSetting
path="hideShoutbox"
>
<BooleanSetting path="hideShoutbox">
{{ $t('settings.hide_shoutbox') }}
</BooleanSetting>
</li>

View file

@ -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,
)
},
},
}

View file

@ -101,6 +101,7 @@
<ChoiceSetting
id="mentionLinkDisplay"
path="mentionLinkDisplay"
:local="true"
:options="mentionLinkDisplayOptions"
>
{{ $t('settings.mention_link_display') }}
@ -171,7 +172,10 @@
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideNsfw">
<BooleanSetting
:local="true"
path="hideNsfw"
>
{{ $t('settings.nsfw_clickthrough') }}
</BooleanSetting>
<ul class="setting-list suboptions">
@ -179,6 +183,7 @@
<BooleanSetting
path="preloadImage"
expert="1"
:local="true"
parent-path="hideNsfw"
>
{{ $t('settings.preload_images') }}
@ -188,6 +193,7 @@
<BooleanSetting
path="useOneClickNsfw"
expert="1"
:local="true"
parent-path="hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}

View file

@ -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:",

View file

@ -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 () => {

View file

@ -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.
}

View file

@ -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,
}

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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