+
+
+ {{ ' ' }}
+
{{ backendDescriptionLabel + ' ' }}
@@ -29,13 +36,6 @@
:value="realDraftMode ? draft :state"
@change="update"
>
- {{ ' ' }}
-
-
-
setMediaFile({ event, index })"
/>
diff --git a/src/components/settings_modal/tabs/clutter_tab.vue b/src/components/settings_modal/tabs/clutter_tab.vue
index fe75582ed..8ae416ff8 100644
--- a/src/components/settings_modal/tabs/clutter_tab.vue
+++ b/src/components/settings_modal/tabs/clutter_tab.vue
@@ -62,12 +62,18 @@
-
+
{{ $t('settings.hide_attachments_in_tl') }}
-
+
{{ $t('settings.hide_attachments_in_convo') }}
diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js
index 908582659..b2ddbe54a 100644
--- a/src/components/settings_modal/tabs/composing_tab.js
+++ b/src/components/settings_modal/tabs/composing_tab.js
@@ -12,6 +12,7 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
import UnitSetting from '../helpers/unit_setting.vue'
import { useInstanceStore } from 'src/stores/instance.js'
+import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import localeService from 'src/services/locale/locale.service.js'
@@ -104,7 +105,7 @@ const ComposingTab = {
},
computed: {
postFormats() {
- return useInstanceStore().postFormats || []
+ return useInstanceCapabilitiesStore().postFormats
},
postContentOptions() {
return this.postFormats.map((format) => ({
diff --git a/src/components/settings_modal/tabs/composing_tab.vue b/src/components/settings_modal/tabs/composing_tab.vue
index 4b6c0bb51..755539096 100644
--- a/src/components/settings_modal/tabs/composing_tab.vue
+++ b/src/components/settings_modal/tabs/composing_tab.vue
@@ -29,6 +29,7 @@
id="postContentType"
path="postContentType"
:options="postContentOptions"
+ :local="true"
>
{{ $t('settings.default_post_status_content_type') }}
@@ -90,6 +91,7 @@
{{ $t('settings.image_compression') }}
@@ -98,6 +100,7 @@
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 905a2baec..a3cb5be07 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -68,7 +68,6 @@
name="ui"
:label="$t('settings.style.fonts.components_inline.interface')"
:fallback="{ family: 'sans-serif' }"
- :is-local="true"
no-inherit="1"
@update:model-value="v => updateFont('interface', v)"
/>
@@ -77,7 +76,6 @@
updateFont('input', v)"
@@ -100,6 +98,7 @@
{{ $t('settings.emoji_reactions_scale') }}
diff --git a/src/components/settings_modal/tabs/layout_tab.vue b/src/components/settings_modal/tabs/layout_tab.vue
index acae859a0..ca514e0cb 100644
--- a/src/components/settings_modal/tabs/layout_tab.vue
+++ b/src/components/settings_modal/tabs/layout_tab.vue
@@ -40,6 +40,7 @@
@@ -83,12 +84,18 @@
-
+
{{ $t('settings.right_sidebar') }}
-
+
{{ $t('settings.navbar_column_stretch') }}
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
index fdf766f58..d484303c1 100644
--- a/src/components/user_avatar/user_avatar.js
+++ b/src/components/user_avatar/user_avatar.js
@@ -37,7 +37,7 @@ const UserAvatar = {
data() {
return {
showPlaceholder: false,
- defaultAvatar: `${useInstanceStore().server + useInstanceStore().defaultAvatar}`,
+ defaultAvatar: `${useInstanceStore().server + useInstanceStore().instanceIdentity.defaultAvatar}`,
betterShadow: useInterfaceStore().browserSupport.cssFilter,
}
},
diff --git a/src/modules/api.js b/src/modules/api.js
index d6bf27f23..6f4b8b15f 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -345,7 +345,7 @@ const api = {
// Set up websocket connection
const token = state.wsToken
if (
- useInstanceStore().shoutAvailable &&
+ useInstanceCapabilitiesStore().shoutAvailable &&
typeof token !== 'undefined' &&
state.socket === null
) {
diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js
index af2f60ac4..fbfaf08a3 100644
--- a/src/modules/default_config_state.js
+++ b/src/modules/default_config_state.js
@@ -45,7 +45,6 @@ export const instanceDefaultConfig = {
muteSensitiveStatuses: false,
collapseMessageWithSubject: false,
padEmoji: true,
- hideAttachments: false,
hideAttachmentsInConv: false,
hideScrobbles: false,
hideScrobblesAfter: '2d',
@@ -95,12 +94,9 @@ export const instanceDefaultConfig = {
webPushAlwaysShowNotifications: false,
interfaceLanguage: browserLocale,
hideScopeNotice: false,
- useStreamingApi: false,
- sidebarRight: false,
scopeCopy: true,
subjectLineBehavior: 'email',
alwaysShowSubjectInput: true,
- postContentType: 'text/plain',
minimalScopesMode: false,
// This hides statuses filtered via a word filter
@@ -133,19 +129,8 @@ export const instanceDefaultConfig = {
userPopoverOverlay: false,
userCardLeftJustify: false,
userCardHidePersonalMarks: false,
- sidebarColumnWidth: '25rem',
- contentColumnWidth: '45rem',
- notifsColumnWidth: '25rem',
- themeEditorMinWidth: '0rem',
- emojiReactionsScale: 0.5,
- textSize: '1rem',
- emojiSize: '2.2rem',
- navbarSize: '3.5rem',
- panelHeaderSize: '3.2rem',
forcedRoundness: -1,
- navbarColumnStretch: false,
greentext: false,
- mentionLinkDisplay: 'short',
mentionLinkShowTooltip: true,
mentionLinkShowAvatar: false,
mentionLinkFadeDomain: true,
@@ -175,8 +160,27 @@ export const instanceDefaultConfig = {
useAbsoluteTimeFormat: false,
absoluteTimeFormatMinAge: '0d',
absoluteTime12h: '24h',
- imageCompression: true,
+}
+
+export const defaultConfigLocal = {
+ hideAttachments: false,
+ hideAttachmentsInConv: false,
+ postContentType: 'text/plain',
+ sidebarRight: false,
+ sidebarColumnWidth: '25rem',
+ contentColumnWidth: '45rem',
+ notifsColumnWidth: '25rem',
+ themeEditorMinWidth: '0rem',
+ emojiReactionsScale: 0.5,
+ textSize: '1rem',
+ emojiSize: '2.2rem',
+ navbarSize: '3.5rem',
+ panelHeaderSize: '3.2rem',
+ navbarColumnStretch: false,
+ mentionLinkDisplay: 'short',
alwaysUseJpeg: false,
+ imageCompression: true,
+ useStreamingApi: false,
}
export const makeUndefined = (c) =>
diff --git a/src/modules/old_default_config_state.js b/src/modules/old_default_config_state.js
index 3977a5d89..e53cafe29 100644
--- a/src/modules/old_default_config_state.js
+++ b/src/modules/old_default_config_state.js
@@ -1,129 +1,187 @@
// this is a snapshot of config keys used prior to sync config.
// used to migrate from old config.
-export const defaultStateKeys = [
- 'expertLevel',
- 'hideISP',
- 'hideInstanceWallpaper',
- 'hideShoutbox',
- 'hideMutedPosts',
- 'hideMutedThreads',
- 'hideWordFilteredPosts',
- 'muteBotStatuses',
- 'muteSensitiveStatuses',
- 'collapseMessageWithSubject',
- 'padEmoji',
- 'hideAttachments',
- 'hideAttachmentsInConv',
- 'hideScrobbles',
- 'hideScrobblesAfter',
- 'maxThumbnails',
- 'hideNsfw',
- 'preloadImage',
- 'loopVideo',
- 'loopVideoSilentOnly',
- 'streaming',
- 'emojiReactionsOnTimeline',
- 'alwaysShowNewPostButton',
- 'autohideFloatingPostButton',
- 'pauseOnUnfocused',
- 'stopGifs',
- 'replyVisibility',
- 'thirdColumnMode',
- 'notificationVisibility',
- 'notificationNative',
- 'webPushNotifications',
- 'webPushAlwaysShowNotifications',
- 'interfaceLanguage',
- 'hideScopeNotice',
- 'useStreamingApi',
- 'sidebarRight',
- 'scopeCopy',
- 'subjectLineBehavior',
- 'alwaysShowSubjectInput',
- 'postContentType',
- 'minimalScopesMode',
- 'hideFilteredStatuses',
- 'modalOnRepeat',
- 'modalOnUnfollow',
- 'modalOnBlock',
- 'modalOnMute',
- 'modalOnMuteConversation',
- 'modalOnMuteDomain',
- 'modalOnDelete',
- 'modalOnLogout',
- 'modalOnApproveFollow',
- 'modalOnDenyFollow',
- 'modalOnRemoveUserFromFollowers',
- 'onMuteDefaultAction',
- 'onBlockDefaultAction',
- 'modalMobileCenter',
- 'playVideosInModal',
- 'useOneClickNsfw',
- 'useContainFit',
- 'disableStickyHeaders',
- 'showScrollbars',
- 'userPopoverAvatarAction',
- 'userPopoverOverlay',
- 'userCardLeftJustify',
- 'userCardHidePersonalMarks',
- 'sidebarColumnWidth',
- 'contentColumnWidth',
- 'notifsColumnWidth',
- 'themeEditorMinWidth',
- 'emojiReactionsScale',
- 'textSize',
- 'emojiSize',
- 'navbarSize',
- 'panelHeaderSize',
- 'forcedRoundness',
- 'navbarColumnStretch',
- 'greentext',
- 'mentionLinkDisplay',
- 'mentionLinkShowTooltip',
- 'mentionLinkShowAvatar',
- 'mentionLinkFadeDomain',
- 'mentionLinkShowYous',
- 'mentionLinkBoldenYou',
- 'hidePostStats',
- 'hideBotIndication',
- 'hideUserStats',
- 'virtualScrolling',
- 'sensitiveByDefault',
- 'conversationDisplay',
- 'conversationTreeAdvanced',
- 'conversationOtherRepliesButton',
- 'conversationTreeFadeAncestors',
- 'showExtraNotifications',
- 'showExtraNotificationsTip',
- 'showChatsInExtraNotifications',
- 'showAnnouncementsInExtraNotifications',
- 'showFollowRequestsInExtraNotifications',
- 'maxDepthInThread',
- 'autocompleteSelect',
- 'closingDrawerMarksAsSeen',
- 'unseenAtTop',
- 'ignoreInactionableSeen',
- 'unsavedPostAction',
- 'autoSaveDraft',
- 'useAbsoluteTimeFormat',
- 'absoluteTimeFormatMinAge',
- 'absoluteTime12h',
- 'imageCompression',
- 'alwaysUseJpeg',
- 'theme',
- 'colors',
+// commented entries are unsynced stuff
+export const defaultConfigSync = {
+ 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,
+ 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
+ /// 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',
+ notificationVisibility: {
+ 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,
+ },
+ webPushNotifications: false,
+ webPushAlwaysShowNotifications: false,
+ //interfaceLanguage: '',
+ hideScopeNotice: false,
+ useStreamingApi: false,
+ sidebarRight: false,
+ scopeCopy: true,
+ subjectLineBehavior: 'email',
+ alwaysShowSubjectInput: true,
+ postContentType: 'text/plain',
+ minimalScopesMode: false,
- 'customTheme',
- 'customThemeSource',
+ // This hides statuses filtered via a word filter
+ hideFilteredStatuses: false,
- 'style',
- 'styleCustomData',
- 'palette',
- 'paletteCustomData',
- 'themeDebug',
- 'forceThemeRecompilation',
- 'theme3hacks',
- // 'muteWords', // mutes migrated separately
- // 'highlight', // highlight migration is done separately
-]
+ // Confirmations
+ modalOnRepeat: false,
+ modalOnUnfollow: false,
+ modalOnBlock: true,
+ modalOnMute: false,
+ modalOnMuteConversation: false,
+ modalOnMuteDomain: true,
+ modalOnDelete: true,
+ modalOnLogout: true,
+ modalOnApproveFollow: false,
+ modalOnDenyFollow: false,
+ modalOnRemoveUserFromFollowers: false,
+
+ // Expiry confirmations/default actions
+ onMuteDefaultAction: 'ask',
+ onBlockDefaultAction: 'ask',
+
+ modalMobileCenter: false,
+ playVideosInModal: false,
+ useOneClickNsfw: 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',
+ theme3hacks: {
+ // Hacks, user overrides that are independent of theme used
+ underlay: 'none',
+ fonts: {
+ interface: undefined,
+ input: undefined,
+ post: undefined,
+ monospace: undefined,
+ },
+ },
+ // Special processing
+ // Theme stuff
+ theme: undefined, // Very old theme store, stores preset name, still in use
+
+ // V1
+ colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
+
+ // V2
+ customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
+ customThemeSource: undefined, // "source", stores original theme data
+
+ // V3
+ style: null,
+ styleCustomData: null,
+ palette: null,
+ paletteCustomData: null,
+ 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
+
+ // Those are handled outside config now
+ // muteWords: [],
+ // highlight: {},
+}
+
+export const defaultConfigLocal = {
+ hideAttachments: false,
+ hideAttachmentsInConv: false,
+ sidebarColumnWidth: '25rem',
+ contentColumnWidth: '45rem',
+ notifsColumnWidth: '25rem',
+ themeEditorMinWidth: '0rem',
+ emojiReactionsScale: 0.5,
+ textSize: '1rem',
+ emojiSize: '2.2rem',
+ navbarSize: '3.5rem',
+ panelHeaderSize: '3.2rem',
+ navbarColumnStretch: false,
+ mentionLinkDisplay: 'short',
+ imageCompression: true,
+ alwaysUseJpeg: false,
+ imageCompression: true,
+ alwaysUseJpeg: false,
+}
diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js
index c460b38b0..e43356e8e 100644
--- a/src/stores/sync_config.js
+++ b/src/stores/sync_config.js
@@ -22,7 +22,7 @@ import { 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 { defaultStateKeys } from 'src/modules/old_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
@@ -38,7 +38,6 @@ export const defaultState = {
// storage of flags - stuff that can only be set and incremented
flagStorage: {
updateCounter: 0, // Counter for most recent update notification seen
- configMigration: 0, // Counter for config -> server-side migrations
reset: 0, // special flag that can be used to force-reset all data, debug purposes only
// special reset codes:
// 1000: trim keys to those known by currently running FE
@@ -417,14 +416,6 @@ export const _doMigrations = async (data, setPreference) => {
console.debug(
'Data has older version, seeing if there any migrations that can be applied',
)
-
- if (data._version === 1) {
- // Migrate old config to sync config
- const vuexState = await storage.getItem('vuex-lz')
- defaultStateKeys.forEach((key) => {
- setPreference({ path: `simple.${key}`, value: vuexState.config[key] })
- })
- }
}
if (data._version > VERSION) {
@@ -623,6 +614,31 @@ export const useSyncConfigStore = defineStore('sync_config', {
const flagsTemplate = userNew ? newUserFlags : defaultState.flagStorage
let dirty = false
+ console.debug('Migrating from old config')
+ const vuexState = await storage.getItem('vuex-lz')
+ const { config } = vuexState
+
+ const migratedEntries = new Set(config._syncMigration ?? [])
+ console.debug(`Already migrated Values: ${[...migratedEntries].join()}`)
+
+ Object.entries(defaultConfigSync).forEach(([key, value]) => {
+ const oldValue = config[key]
+ const defaultValue = value
+
+ const present = oldValue !== undefined
+ const migrated = migratedEntries.has(key)
+ const different = !isEqual(oldValue, defaultValue)
+
+ if (present && !migrated && different) {
+ 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)
+
if (recent === null) {
console.debug(
`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`,
@@ -641,7 +657,12 @@ export const useSyncConfigStore = defineStore('sync_config', {
// discarding timestamps and versions
const { _timestamp: _0, _version: _1, ...recentData } = recent
const { _timestamp: _2, _version: _3, ...staleData } = stale
- dirty = !isEqual(recentData, staleData)
+ dirty = !isEqual(
+ // Something wrong happens if we compare both objects directly
+ // or with cloneDeep()
+ JSON.parse(JSON.stringify(recentData)),
+ JSON.parse(JSON.stringify(staleData)),
+ )
console.debug(`Data ${dirty ? 'needs' : "doesn't need"} merging`)
}