From 8bbc17f9827e921d377e0adf0dd9d697a166fe05 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 21 Jan 2026 15:59:13 +0200 Subject: [PATCH 01/29] fix sss not saving cache locally --- src/stores/serverSideStorage.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/stores/serverSideStorage.js b/src/stores/serverSideStorage.js index 99f5045f7..23c027b1e 100644 --- a/src/stores/serverSideStorage.js +++ b/src/stores/serverSideStorage.js @@ -16,6 +16,10 @@ import { defineStore } from 'pinia' import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' +import { + defaultState as configDefaultState, + instanceDefaultConfig, +} from 'src/modules/default_config_state' export const VERSION = 1 export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically @@ -41,6 +45,11 @@ export const defaultState = { dontShowUpdateNotifs: false, collapseNav: false, muteFilters: {}, + ...{ + // reverting all the undefined to their initial values + ...configDefaultState, + ...instanceDefaultConfig, + }, }, collections: { pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'], @@ -128,7 +137,7 @@ export const _getRecentData = (cache, live, isTest) => { live._version === cache._version ) { console.debug( - 'Same version/timestamp on both source, source of truth irrelevant', + 'Same version/timestamp on both sources, source of truth irrelevant', ) result.recent = cache result.stale = live @@ -640,4 +649,9 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { }) }, }, + persist: { + afterLoad(state) { + return state + }, + }, }) From 1942d43eb394dc1cd0a66a80f5e2b65f4831d745 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 21 Jan 2026 16:03:33 +0200 Subject: [PATCH 02/29] fix the lint-fix command for eslint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bdd1acfbe..291b12381 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "ci-eslint": "yarn exec eslint", "ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'", "lint": "yarn ci-biome; yarn ci-eslint; yarn ci-stylelint", - "lint-fix": "yarn exec eslint --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write" + "lint-fix": "yarn exec eslint -- --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write" }, "dependencies": { "@babel/runtime": "7.28.4", From 20071d5a11931ac01fd9df9c290ee071e7197ac5 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 21 Jan 2026 16:51:08 +0200 Subject: [PATCH 03/29] clutter page unsync -> sync --- src/App.js | 8 +-- .../post_status_form/post_status_form.js | 6 ++- .../settings_modal/helpers/setting.js | 41 +++++++++++++++ .../helpers/shared_computed_object.js | 28 +++++----- .../settings_modal/tabs/clutter_tab.vue | 52 ++++++++++++++----- src/components/status/status.js | 25 ++++----- .../status_content/status_content.js | 19 ++++--- src/components/user_card/user_card.js | 17 +++--- src/components/user_card/user_card.vue | 6 +-- src/services/api/api.service.js | 4 +- 10 files changed, 144 insertions(+), 62 deletions(-) diff --git a/src/App.js b/src/App.js index 33645c63d..2af10cc8b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,9 @@ import { throttle } from 'lodash' +import { mapState } from 'pinia' import { defineAsyncComponent } from 'vue' import { mapGetters } from 'vuex' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import DesktopNav from './components/desktop_nav/desktop_nav.vue' import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue' import FeaturesPanel from './components/features_panel/features_panel.vue' @@ -184,9 +186,6 @@ export default { shoutboxPosition() { return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false }, - hideShoutbox() { - return this.$store.getters.mergedConfig.hideShoutbox - }, layoutType() { return useInterfaceStore().layoutType }, @@ -214,6 +213,9 @@ export default { return window /* this.$refs.appContentRef */ }, ...mapGetters(['mergedConfig']), + ...mapState(useServerSideStorageStore, { + hideShoutbox: (store) => store.prefsStorage.simple.hideShoutbox, + }), }, methods: { resizeHandler() { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 396aa94f0..a073a5eb8 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -8,6 +8,7 @@ import Popover from 'src/components/popover/popover.vue' import { pollFormToMasto } from 'src/services/poll/poll.service.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMediaViewerStore } from 'src/stores/media_viewer.js' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' import fileTypeService from '../../services/file_type/file_type.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js' @@ -299,10 +300,11 @@ const PostStatusForm = { return this.hasStatusLengthLimit && this.charactersLeft < 0 }, minimalScopesMode() { - return this.$store.state.instance.minimalScopesMode + return useServerSideStorageStore().prefsStorage.simple.minimalScopesMode }, alwaysShowSubject() { - return this.mergedConfig.alwaysShowSubjectInput + return useServerSideStorageStore().prefsStorage.simple + .alwaysShowSubjectInput }, postFormats() { return this.$store.state.instance.postFormats || [] diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index c77de92b1..06ed9b2f6 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -1,5 +1,6 @@ import { cloneDeep, get, isEqual, set } from 'lodash' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import DraftButtons from './draft_buttons.vue' import ModifiedIndicator from './modified_indicator.vue' import ProfileSettingIndicator from './profile_setting_indicator.vue' @@ -226,6 +227,8 @@ export default { }, configSource() { switch (this.realSource) { + case 'server-side': + return useServerSideStorageStore().prefsStorage case 'profile': return this.$store.state.profileConfig case 'admin': @@ -239,6 +242,39 @@ export default { return (k, v) => this.$emit('update:modelValue', v) } switch (this.realSource) { + case 'server-side': { + return (path, value, operator) => { + const folder = path.split('.')[0] + if (folder === 'collections' || folder === 'objectCollections') { + switch (operator) { + case 'add': + useServerSideStorageStore().addCollectionPreference({ + path, + value, + }) + useServerSideStorageStore().pushServerSideStorage() + break + case 'remove': + useServerSideStorageStore().removeCollectionPreference({ + path, + value, + }) + useServerSideStorageStore().pushServerSideStorage() + break + default: + console.error( + `Unknown server-side collection operator ${operator}, ignoring`, + ) + break + } + } else if (folder === 'simple') { + useServerSideStorageStore().setPreference({ path, value }) + useServerSideStorageStore().pushServerSideStorage() + } else { + console.error(`Unknown server-side folder ${folder}, ignoring`) + } + } + } case 'profile': return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v }) @@ -262,6 +298,11 @@ export default { switch (this.realSource) { case 'profile': return {} + case 'server-side': + return get( + this.$store.getters.defaultConfig, + this.path.split(/\./g).slice(1), + ) default: return get(this.$store.getters.defaultConfig, this.path) } diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js index b8197b639..070a35b44 100644 --- a/src/components/settings_modal/helpers/shared_computed_object.js +++ b/src/components/settings_modal/helpers/shared_computed_object.js @@ -1,18 +1,20 @@ +import { mapState as mapPiniaState } from 'pinia' +import { mapGetters, mapState } from 'vuex' + +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' + const SharedComputedObject = () => ({ - user() { - return this.$store.state.users.currentUser - }, + ...mapPiniaState(useServerSideStorageStore, { + serverSide: (store) => store.state.prefsStorage, + }), + ...mapGetters(['mergedConfig']), + ...mapState({ + adminConfig: (state) => state.adminSettings.config, + adminDraft: (state) => state.adminSettings.draft, + user: (state) => state.users.currentUser, + }), expertLevel() { - return this.$store.getters.mergedConfig.expertLevel > 0 - }, - mergedConfig() { - return this.$store.getters.mergedConfig - }, - adminConfig() { - return this.$store.state.adminSettings.config - }, - adminDraft() { - return this.$store.state.adminSettings.draft + return this.mergedConfig.expertLevel > 0 }, }) diff --git a/src/components/settings_modal/tabs/clutter_tab.vue b/src/components/settings_modal/tabs/clutter_tab.vue index daf098f5a..ef230dee0 100644 --- a/src/components/settings_modal/tabs/clutter_tab.vue +++ b/src/components/settings_modal/tabs/clutter_tab.vue @@ -4,41 +4,58 @@

{{ $t('settings.interface') }}

  • - + {{ $t('settings.subject_input_always_show') }}
  • - + {{ $t('settings.minimal_scopes_mode') }}
  • - + {{ $t('settings.hide_post_stats') }}
  • {{ $t('settings.hide_user_stats') }}
  • - + {{ $t('settings.hide_actor_type_indication') }}
  • - + {{ $t('settings.hide_scrobbles') }}
    • @@ -52,30 +69,41 @@
      • {{ $t('settings.max_thumbnails') }}
      • - + {{ $t('settings.hide_attachments_in_tl') }}
      • - + {{ $t('settings.hide_attachments_in_convo') }}
      • - + {{ $t('settings.user_card_hide_personal_marks') }}
      • {{ $t('settings.hide_shoutbox') }} diff --git a/src/components/status/status.js b/src/components/status/status.js index 1673a6933..8cbc12c09 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -1,4 +1,5 @@ import { unescape as ldUnescape, uniqBy } from 'lodash' +import { mapState } from 'pinia' import MentionLink from 'src/components/mention_link/mention_link.vue' import MentionsLine from 'src/components/mentions_line/mentions_line.vue' @@ -254,12 +255,7 @@ const Status = { return !!this.currentUser }, muteFilterHits() { - return muteFilterHits( - Object.values( - useServerSideStorageStore().prefsStorage.simple.muteFilters, - ), - this.status, - ) + return muteFilterHits(Object.values(this.muteFilters), this.status) }, botStatus() { return this.status.user.actor_type === 'Service' @@ -452,9 +448,6 @@ const Status = { .map((tagObj) => tagObj.name) .join(' ') }, - hidePostStats() { - return this.mergedConfig.hidePostStats - }, shouldDisplayFavsAndRepeats() { return ( !this.hidePostStats && @@ -511,10 +504,10 @@ const Status = { return this.quotedStatus && this.displayQuote }, scrobblePresent() { - if (this.mergedConfig.hideScrobbles) return false + if (this.hideScrobbles) return false if (!this.status.user?.latestScrobble) return false - const value = this.mergedConfig.hideScrobblesAfter.match(/\d+/gs)[0] - const unit = this.mergedConfig.hideScrobblesAfter.match(/\D+/gs)[0] + const value = this.hideScrobblesAfter.match(/\d+/gs)[0] + const unit = this.hideScrobblesAfter.match(/\D+/gs)[0] let multiplier = 60 * 1000 // minutes is smallest unit switch (unit) { case 'm': @@ -536,6 +529,14 @@ const Status = { scrobble() { return this.status.user?.latestScrobble }, + ...mapState(useServerSideStorageStore, { + muteFilters: (store) => store.prefsStorage.simple.muteFilters, + hideBotIndicatior: (store) => store.prefsStorage.simple.hideBotIndicator, + hidePostStats: (store) => store.prefsStorage.simple.hidePostStats, + hideScrobbles: (store) => store.prefsStorage.simple.hideScrobbles, + hideScrobblesAfter: (store) => + store.prefsStorage.simple.hideScrobblesAfter, + }), }, methods: { visibilityIcon(visibility) { diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index 51bfe6f1e..c93423a7a 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -1,7 +1,9 @@ +import { mapState as mapPiniaState } from 'pinia' import { mapGetters, mapState } from 'vuex' import StatusBody from 'src/components/status_body/status_body.vue' import { useMediaViewerStore } from 'src/stores/media_viewer' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import Attachment from '../attachment/attachment.vue' import Gallery from '../gallery/gallery.vue' import LinkPreview from '../link-preview/link-preview.vue' @@ -90,8 +92,8 @@ const StatusContent = { }, hideAttachments() { return ( - (this.mergedConfig.hideAttachments && !this.inConversation) || - (this.mergedConfig.hideAttachmentsInConv && this.inConversation) + (this.hideAttachments && !this.inConversation) || + (this.hideAttachmentsInConv && this.inConversation) ) }, nsfwClickthrough() { @@ -110,21 +112,24 @@ const StatusContent = { if (this.compact) { return 'small' } else if ( - (this.mergedConfig.hideAttachments && !this.inConversation) || - (this.mergedConfig.hideAttachmentsInConv && this.inConversation) || + (this.hideAttachments && !this.inConversation) || + (this.hideAttachmentsInConv && this.inConversation) || this.status.attachments.length > this.maxThumbnails ) { return 'hide' } return 'normal' }, - maxThumbnails() { - return this.mergedConfig.maxThumbnails - }, ...mapGetters(['mergedConfig']), ...mapState({ currentUser: (state) => state.users.currentUser, }), + ...mapPiniaState(useServerSideStorageStore, { + maxThumbnails: (store) => store.prefsStorage.simple.maxThumbnails, + hideAttachments: (store) => store.prefsStorage.simple.hideAttachments, + hideAttachmentsInConv: (store) => + store.prefsStorage.simple.hideAttachmentsInConv, + }), }, components: { Attachment, diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 0786e5b35..664b70fe3 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -1,6 +1,7 @@ import isEqual from 'lodash/isEqual' import merge from 'lodash/merge' import ldUnescape from 'lodash/unescape' +import { mapState } from 'pinia' import { mapGetters } from 'vuex' import Checkbox from 'src/components/checkbox/checkbox.vue' @@ -15,6 +16,7 @@ import { propsToNative } from 'src/services/attributes_helper/attributes_helper. import localeService from 'src/services/locale/locale.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { usePostStatusStore } from 'src/stores/post_status' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { useInterfaceStore } from '../../stores/interface' import { useMediaViewerStore } from '../../stores/media_viewer' import AccountActions from '../account_actions/account_actions.vue' @@ -217,13 +219,11 @@ export default { }, userHighlightType: { get() { - const data = - this.$store.getters.mergedConfig.highlight[this.user.screen_name] + const data = this.mergedConfig.highlight[this.user.screen_name] return (data && data.type) || 'disabled' }, set(type) { - const data = - this.$store.getters.mergedConfig.highlight[this.user.screen_name] + const data = this.mergedConfig.highlight[this.user.screen_name] if (type !== 'disabled') { this.$store.dispatch('setHighlight', { user: this.user.screen_name, @@ -237,12 +237,10 @@ export default { }) } }, - ...mapGetters(['mergedConfig']), }, userHighlightColor: { get() { - const data = - this.$store.getters.mergedConfig.highlight[this.user.screen_name] + const data = this.mergedConfig.highlight[this.user.screen_name] return data && data.color }, set(color) { @@ -386,6 +384,11 @@ export default { }) }, ...mapGetters(['mergedConfig']), + ...mapState(useServerSideStorageStore, { + hideUserStats: (store) => store.prefsStorage.simple.hideUserStats, + userCardHidePersonalMarks: (store) => + store.prefsStorage.simple.userCardHidePersonalMarks, + }), }, methods: { muteUser() { diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 66529f59b..e3b061e0c 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -293,7 +293,7 @@
        {{ user.statuses_count }}
        diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 90886b4c5..48f8b9e8a 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -318,9 +318,7 @@ const updateProfileJSON = ({ credentials, params }) => { credentials, payload: params, method: 'PATCH', - }) - .then((data) => data.json()) - .then((data) => parseUser(data)) + }).then((data) => parseUser(data)) } // Params needed: From 9452b3084a51e41a520470834878aa4a37205597 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 22 Jan 2026 00:22:18 +0200 Subject: [PATCH 04/29] some initial work on moving instance settings to pinia --- src/App.js | 41 +- src/boot/after_store.js | 201 ++++----- .../post_status_form/post_status_form.js | 11 +- .../settings_modal/helpers/setting.js | 42 +- .../settings_modal/tabs/clutter_tab.vue | 24 +- .../tabs/old_theme_tab/old_theme_tab.js | 6 +- src/components/status/status.js | 7 +- src/modules/default_config_state.js | 2 +- src/stores/instance.js | 387 ++++++++++++++++++ src/stores/serverSideStorage.js | 33 +- 10 files changed, 559 insertions(+), 195 deletions(-) create mode 100644 src/stores/instance.js diff --git a/src/App.js b/src/App.js index 2af10cc8b..bfdeb2be4 100644 --- a/src/App.js +++ b/src/App.js @@ -22,6 +22,7 @@ import UserReportingModal from './components/user_reporting_modal/user_reporting import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import { getOrCreateServiceWorker } from './services/sw/sw' import { windowHeight, windowWidth } from './services/window_utils/window_utils' +import { useInstanceStore } from './stores/instance' import { useInterfaceStore } from './stores/interface' import { useShoutStore } from './stores/shout' @@ -135,11 +136,6 @@ export default { userBackground() { return this.currentUser.background_image }, - instanceBackground() { - return this.mergedConfig.hideInstanceWallpaper - ? null - : this.$store.state.instance.background - }, background() { return this.userBackground || this.instanceBackground }, @@ -153,16 +149,6 @@ export default { shout() { return useShoutStore().joined }, - suggestionsEnabled() { - return this.$store.state.instance.suggestionsEnabled - }, - showInstanceSpecificPanel() { - return ( - this.$store.state.instance.showInstanceSpecificPanel && - !this.$store.getters.mergedConfig.hideISP && - this.$store.state.instance.instanceSpecificPanelContent - ) - }, isChats() { return this.$route.name === 'chat' || this.$route.name === 'chats' }, @@ -177,21 +163,12 @@ export default { this.layoutType === 'mobile' ) }, - showFeaturesPanel() { - return this.$store.state.instance.showFeaturesPanel - }, - editingAvailable() { - return this.$store.state.instance.editingAvailable - }, shoutboxPosition() { return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false }, layoutType() { return useInterfaceStore().layoutType }, - privateMode() { - return this.$store.state.instance.private - }, reverseLayout() { const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig @@ -214,8 +191,22 @@ export default { }, ...mapGetters(['mergedConfig']), ...mapState(useServerSideStorageStore, { - hideShoutbox: (store) => store.prefsStorage.simple.hideShoutbox, + hideShoutbox: (store) => store.mergedConfig.hideShoutbox, }), + ...mapState(useInstanceStore, { + instanceBackground: (store) => + this.mergedConfig.hideInstanceWallpaper ? null : store.background, + showInstanceSpecificPanel: (store) => + store.showInstanceSpecificPanel && + !this.$store.getters.mergedConfig.hideISP && + store.instanceSpecificPanelContent, + }), + ...mapState(useInstanceStore, [ + 'editingAvailable', + 'showFeaturesPanel', + 'private', + 'suggestionsEnabled', + ]), }, methods: { resizeHandler() { diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 1a2be5bd7..48dc42bc1 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -1,6 +1,7 @@ /* global process */ import vClickOutside from 'click-outside-vue3' +import { get, set } from 'lodash' import { createApp } from 'vue' import { createRouter, createWebHistory } from 'vue-router' import VueVirtualScroller from 'vue-virtual-scroller' @@ -22,6 +23,7 @@ import { import { useAnnouncementsStore } from 'src/stores/announcements' import { useAuthFlowStore } from 'src/stores/auth_flow' import { useI18nStore } from 'src/stores/i18n' +import { useInstanceStore } from 'src/stores/instance' import { useInterfaceStore } from 'src/stores/interface' import { useOAuthStore } from 'src/stores/oauth' import App from '../App.vue' @@ -78,30 +80,30 @@ const getInstanceConfig = async ({ store }) => { const textlimit = data.max_toot_chars const vapidPublicKey = data.pleroma.vapid_public_key - store.dispatch('setInstanceOption', { - name: 'pleromaExtensionsAvailable', + useInstanceStore().set({ + path: 'featureSet.pleromaExtensionsAvailable', value: data.pleroma, }) - store.dispatch('setInstanceOption', { - name: 'textlimit', + useInstanceStore().set({ + path: 'textlimit', value: textlimit, }) - store.dispatch('setInstanceOption', { - name: 'accountApprovalRequired', + useInstanceStore().set({ + path: 'accountApprovalRequired', value: data.approval_required, }) - store.dispatch('setInstanceOption', { - name: 'birthdayRequired', + useInstanceStore().set({ + path: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required, }) - store.dispatch('setInstanceOption', { - name: 'birthdayMinAge', + useInstanceStore().set({ + path: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0, }) if (vapidPublicKey) { - store.dispatch('setInstanceOption', { - name: 'vapidPublicKey', + useInstanceStore().set({ + path: 'vapidPublicKey', value: vapidPublicKey, }) } @@ -156,19 +158,32 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { let config = {} if (overrides.staticConfigPreference && env === 'development') { console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') - config = Object.assign({}, apiConfig, staticConfig) + config = { ...apiConfig, ...staticConfig } } else { - config = Object.assign({}, staticConfig, apiConfig) + config = { ...staticConfig, ...apiConfig } } + console.trace(config) - const copyInstanceOption = (name) => { - if (typeof config[name] !== 'undefined') { - store.dispatch('setInstanceOption', { name, value: config[name] }) + const copyInstanceIdentityOption = (path) => { + if (get(config, path) !== undefined) { + useInstanceStore().set({ + path: `instanceIdentity.${path}`, + value: get(config, path), + }) } } - Object.keys(staticOrApiConfigDefault).forEach(copyInstanceOption) - Object.keys(instanceDefaultConfig).forEach(copyInstanceOption) + const copyInstancePrefOption = (path) => { + if (get(config, path) !== undefined) { + useInstanceStore().set({ + path: `prefsStorage.${path}`, + value: get(config, path), + }) + } + } + + Object.keys(staticOrApiConfigDefault).forEach(copyInstanceIdentityOption) + Object.keys(instanceDefaultConfig).forEach(copyInstancePrefOption) useAuthFlowStore().setInitialStrategy(config.loginMethod) } @@ -178,7 +193,7 @@ const getTOS = async ({ store }) => { const res = await window.fetch('/static/terms-of-service.html') if (res.ok) { const html = await res.text() - store.dispatch('setInstanceOption', { name: 'tos', value: html }) + useInstanceStore().set({ path: 'tos', value: html }) } else { throw res } @@ -192,8 +207,8 @@ const getInstancePanel = async ({ store }) => { const res = await preloadFetch('/instance/panel.html') if (res.ok) { const html = await res.text() - store.dispatch('setInstanceOption', { - name: 'instanceSpecificPanelContent', + useInstanceStore().set({ + path: 'instanceSpecificPanelContent', value: html, }) } else { @@ -227,7 +242,7 @@ const getStickers = async ({ store }) => { ).sort((a, b) => { return a.meta.title.localeCompare(b.meta.title) }) - store.dispatch('setInstanceOption', { name: 'stickers', value: stickers }) + useInstanceStore().set({ path: 'stickers', value: stickers }) } else { throw res } @@ -248,8 +263,8 @@ const getAppSecret = async ({ store }) => { const resolveStaffAccounts = ({ store, accounts }) => { const nicknames = accounts.map((uri) => uri.split('/').pop()) - store.dispatch('setInstanceOption', { - name: 'staffAccounts', + useInstanceStore().set({ + path: 'staffAccounts', value: nicknames, }) } @@ -262,160 +277,160 @@ const getNodeInfo = async ({ store }) => { const data = await res.json() const metadata = data.metadata const features = metadata.features - store.dispatch('setInstanceOption', { - name: 'name', + useInstanceStore().set({ + path: 'name', value: metadata.nodeName, }) - store.dispatch('setInstanceOption', { - name: 'registrationOpen', + useInstanceStore().set({ + path: 'registrationOpen', value: data.openRegistrations, }) - store.dispatch('setInstanceOption', { - name: 'mediaProxyAvailable', + useInstanceStore().set({ + path: 'featureSet.mediaProxyAvailable', value: features.includes('media_proxy'), }) - store.dispatch('setInstanceOption', { - name: 'safeDM', + useInstanceStore().set({ + path: 'featureSet.safeDM', value: features.includes('safe_dm_mentions'), }) - store.dispatch('setInstanceOption', { - name: 'shoutAvailable', + useInstanceStore().set({ + path: 'featureSet.shoutAvailable', value: features.includes('chat'), }) - store.dispatch('setInstanceOption', { - name: 'pleromaChatMessagesAvailable', + useInstanceStore().set({ + path: 'featureSet.pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages'), }) - store.dispatch('setInstanceOption', { - name: 'pleromaCustomEmojiReactionsAvailable', + useInstanceStore().set({ + path: 'featureSet.pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') || features.includes('custom_emoji_reactions'), }) - store.dispatch('setInstanceOption', { - name: 'pleromaBookmarkFoldersAvailable', + useInstanceStore().set({ + path: 'featureSet.pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders'), }) - store.dispatch('setInstanceOption', { - name: 'gopherAvailable', + useInstanceStore().set({ + path: 'featureSet.gopherAvailable', value: features.includes('gopher'), }) - store.dispatch('setInstanceOption', { - name: 'pollsAvailable', + useInstanceStore().set({ + path: 'featureSet.pollsAvailable', value: features.includes('polls'), }) - store.dispatch('setInstanceOption', { - name: 'editingAvailable', + useInstanceStore().set({ + path: 'featureSet.editingAvailable', value: features.includes('editing'), }) - store.dispatch('setInstanceOption', { - name: 'pollLimits', + useInstanceStore().set({ + path: 'pollLimits', value: metadata.pollLimits, }) - store.dispatch('setInstanceOption', { - name: 'mailerEnabled', + useInstanceStore().set({ + path: 'featureSet.mailerEnabled', value: metadata.mailerEnabled, }) - store.dispatch('setInstanceOption', { - name: 'quotingAvailable', + useInstanceStore().set({ + path: 'featureSet.quotingAvailable', value: features.includes('quote_posting'), }) - store.dispatch('setInstanceOption', { - name: 'groupActorAvailable', + useInstanceStore().set({ + path: 'featureSet.groupActorAvailable', value: features.includes('pleroma:group_actors'), }) - store.dispatch('setInstanceOption', { - name: 'blockExpiration', + useInstanceStore().set({ + path: 'featureSet.blockExpiration', value: features.includes('pleroma:block_expiration'), }) - store.dispatch('setInstanceOption', { - name: 'localBubbleInstances', + useInstanceStore().set({ + path: 'featureSet.localBubbleInstances', value: metadata.localBubbleInstances ?? [], }) const uploadLimits = metadata.uploadLimits - store.dispatch('setInstanceOption', { - name: 'uploadlimit', + useInstanceStore().set({ + path: 'uploadlimit', value: parseInt(uploadLimits.general), }) - store.dispatch('setInstanceOption', { - name: 'avatarlimit', + useInstanceStore().set({ + path: 'avatarlimit', value: parseInt(uploadLimits.avatar), }) - store.dispatch('setInstanceOption', { - name: 'backgroundlimit', + useInstanceStore().set({ + path: 'backgroundlimit', value: parseInt(uploadLimits.background), }) - store.dispatch('setInstanceOption', { - name: 'bannerlimit', + useInstanceStore().set({ + path: 'bannerlimit', value: parseInt(uploadLimits.banner), }) - store.dispatch('setInstanceOption', { - name: 'fieldsLimits', + useInstanceStore().set({ + path: 'fieldsLimits', value: metadata.fieldsLimits, }) - store.dispatch('setInstanceOption', { - name: 'restrictedNicknames', + useInstanceStore().set({ + path: 'restrictedNicknames', value: metadata.restrictedNicknames, }) - store.dispatch('setInstanceOption', { - name: 'postFormats', + useInstanceStore().set({ + path: 'featureSet.postFormats', value: metadata.postFormats, }) const suggestions = metadata.suggestions - store.dispatch('setInstanceOption', { - name: 'suggestionsEnabled', + useInstanceStore().set({ + path: 'featureSet.suggestionsEnabled', value: suggestions.enabled, }) - store.dispatch('setInstanceOption', { - name: 'suggestionsWeb', + useInstanceStore().set({ + path: 'featureSet.suggestionsWeb', value: suggestions.web, }) const software = data.software - store.dispatch('setInstanceOption', { - name: 'backendVersion', + useInstanceStore().set({ + path: 'backendVersion', value: software.version, }) - store.dispatch('setInstanceOption', { - name: 'backendRepository', + useInstanceStore().set({ + path: 'backendRepository', value: software.repository, }) const priv = metadata.private - store.dispatch('setInstanceOption', { name: 'private', value: priv }) + useInstanceStore().set({ path: 'private', value: priv }) const frontendVersion = window.___pleromafe_commit_hash - store.dispatch('setInstanceOption', { - name: 'frontendVersion', + useInstanceStore().set({ + path: 'frontendVersion', value: frontendVersion, }) const federation = metadata.federation - store.dispatch('setInstanceOption', { - name: 'tagPolicyAvailable', + useInstanceStore().set({ + path: 'featureSet.tagPolicyAvailable', value: typeof federation.mrf_policies === 'undefined' ? false : metadata.federation.mrf_policies.includes('TagPolicy'), }) - store.dispatch('setInstanceOption', { - name: 'federationPolicy', + useInstanceStore().set({ + path: 'federationPolicy', value: federation, }) - store.dispatch('setInstanceOption', { - name: 'federating', + useInstanceStore().set({ + path: 'federating', value: typeof federation.enabled === 'undefined' ? true : federation.enabled, }) const accountActivationRequired = metadata.accountActivationRequired - store.dispatch('setInstanceOption', { - name: 'accountActivationRequired', + useInstanceStore().set({ + path: 'accountActivationRequired', value: accountActivationRequired, }) @@ -526,7 +541,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { typeof overrides.target !== 'undefined' ? overrides.target : window.location.origin - store.dispatch('setInstanceOption', { name: 'server', value: server }) + useInstanceStore().set({ path: 'server', value: server }) await setConfig({ store }) try { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index a073a5eb8..781b3214d 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -299,13 +299,6 @@ const PostStatusForm = { isOverLengthLimit() { return this.hasStatusLengthLimit && this.charactersLeft < 0 }, - minimalScopesMode() { - return useServerSideStorageStore().prefsStorage.simple.minimalScopesMode - }, - alwaysShowSubject() { - return useServerSideStorageStore().prefsStorage.simple - .alwaysShowSubjectInput - }, postFormats() { return this.$store.state.instance.postFormats || [] }, @@ -412,6 +405,10 @@ const PostStatusForm = { ...mapState(useInterfaceStore, { mobileLayout: (store) => store.mobileLayout, }), + ...mapState(useServerSideStorageStore, { + minimalScopesMode: (store) => store.mergedConfig.minimalScopesMode, + alwaysShowSubject: (store) => store.mergedConfig.alwaysShowSubjectInput, + }), }, watch: { newStatus: { diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index 06ed9b2f6..86e54caad 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -1,5 +1,6 @@ import { cloneDeep, get, isEqual, set } from 'lodash' +import { useInstanceStore } from 'src/stores/instance' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import DraftButtons from './draft_buttons.vue' import ModifiedIndicator from './modified_indicator.vue' @@ -228,7 +229,7 @@ export default { configSource() { switch (this.realSource) { case 'server-side': - return useServerSideStorageStore().prefsStorage + return useServerSideStorageStore().mergedConfig case 'profile': return this.$store.state.profileConfig case 'admin': @@ -243,36 +244,10 @@ export default { } switch (this.realSource) { case 'server-side': { - return (path, value, operator) => { - const folder = path.split('.')[0] - if (folder === 'collections' || folder === 'objectCollections') { - switch (operator) { - case 'add': - useServerSideStorageStore().addCollectionPreference({ - path, - value, - }) - useServerSideStorageStore().pushServerSideStorage() - break - case 'remove': - useServerSideStorageStore().removeCollectionPreference({ - path, - value, - }) - useServerSideStorageStore().pushServerSideStorage() - break - default: - console.error( - `Unknown server-side collection operator ${operator}, ignoring`, - ) - break - } - } else if (folder === 'simple') { - useServerSideStorageStore().setPreference({ path, value }) - useServerSideStorageStore().pushServerSideStorage() - } else { - console.error(`Unknown server-side folder ${folder}, ignoring`) - } + return (originalPath, value, operator) => { + const path = `simple.${originalPath}` + useServerSideStorageStore().setPreference({ path, value }) + useServerSideStorageStore().pushServerSideStorage() } } case 'profile': @@ -299,10 +274,7 @@ export default { case 'profile': return {} case 'server-side': - return get( - this.$store.getters.defaultConfig, - this.path.split(/\./g).slice(1), - ) + return get(useInstanceStore().prefsStorage, this.path) default: return get(this.$store.getters.defaultConfig, this.path) } diff --git a/src/components/settings_modal/tabs/clutter_tab.vue b/src/components/settings_modal/tabs/clutter_tab.vue index ef230dee0..59e942492 100644 --- a/src/components/settings_modal/tabs/clutter_tab.vue +++ b/src/components/settings_modal/tabs/clutter_tab.vue @@ -6,7 +6,7 @@
      • {{ $t('settings.subject_input_always_show') }} @@ -14,7 +14,7 @@
      • {{ $t('settings.minimal_scopes_mode') }} @@ -22,14 +22,14 @@
      • {{ $t('settings.hide_post_stats') }}
      • {{ $t('settings.hide_user_stats') }} @@ -38,7 +38,7 @@
      • {{ $t('settings.hide_actor_type_indication') }} @@ -46,7 +46,7 @@
      • {{ $t('settings.hide_scrobbles') }} @@ -55,7 +55,7 @@ @@ -70,7 +70,7 @@
      • {{ $t('settings.max_thumbnails') }} @@ -79,7 +79,7 @@
      • {{ $t('settings.hide_attachments_in_tl') }} @@ -87,7 +87,7 @@
      • {{ $t('settings.hide_attachments_in_convo') }} @@ -95,7 +95,7 @@
      • {{ $t('settings.user_card_hide_personal_marks') }} @@ -103,7 +103,7 @@
      • {{ $t('settings.hide_shoutbox') }} diff --git a/src/components/settings_modal/tabs/old_theme_tab/old_theme_tab.js b/src/components/settings_modal/tabs/old_theme_tab/old_theme_tab.js index c79c72e70..3235264bc 100644 --- a/src/components/settings_modal/tabs/old_theme_tab/old_theme_tab.js +++ b/src/components/settings_modal/tabs/old_theme_tab/old_theme_tab.js @@ -125,7 +125,7 @@ export default { } }, created() { - const currentIndex = this.$store.state.instance.themesIndex + const currentIndex = this.$store.state.instance.instanceThemesIndex let promise if (currentIndex) { @@ -134,8 +134,8 @@ export default { promise = useInterfaceStore().fetchThemesIndex() } - promise.then((themesIndex) => { - Object.values(themesIndex).forEach((themeFunc) => { + promise.then((instanceThemesIndex) => { + Object.values(instanceThemesIndex).forEach((themeFunc) => { themeFunc().then( (themeData) => themeData && this.availableStyles.push(themeData), ) diff --git a/src/components/status/status.js b/src/components/status/status.js index 8cbc12c09..0fb9de9f0 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -532,10 +532,9 @@ const Status = { ...mapState(useServerSideStorageStore, { muteFilters: (store) => store.prefsStorage.simple.muteFilters, hideBotIndicatior: (store) => store.prefsStorage.simple.hideBotIndicator, - hidePostStats: (store) => store.prefsStorage.simple.hidePostStats, - hideScrobbles: (store) => store.prefsStorage.simple.hideScrobbles, - hideScrobblesAfter: (store) => - store.prefsStorage.simple.hideScrobblesAfter, + hidePostStats: (store) => store.mergedConfig.hidePostStats, + hideScrobbles: (store) => store.mergedConfig.hideScrobbles, + hideScrobblesAfter: (store) => store.mergedConfig.hideScrobblesAfter, }), }, methods: { diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index d5daef3d3..c540dde7c 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -93,7 +93,7 @@ export const instanceDefaultConfig = { sidebarRight: false, scopeCopy: true, subjectLineBehavior: 'email', - alwaysShowSubjectInput: true, + alwaysShowSubjectInput: false, postContentType: 'text/plain', minimalScopesMode: false, diff --git a/src/stores/instance.js b/src/stores/instance.js new file mode 100644 index 000000000..7fcf1a92f --- /dev/null +++ b/src/stores/instance.js @@ -0,0 +1,387 @@ +import { get, set } from 'lodash' +import { defineStore } from 'pinia' + +import { useInterfaceStore } from 'src/stores/interface.js' +import { ensureFinalFallback } from '../i18n/languages.js' +import { instanceDefaultProperties } from '../modules/config.js' +import { + instanceDefaultConfig, + staticOrApiConfigDefault, +} from '../modules/default_config_state.js' +import apiService from '../services/api/api.service.js' + +import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations' + +const SORTED_EMOJI_GROUP_IDS = [ + 'smileys-and-emotion', + 'people-and-body', + 'animals-and-nature', + 'food-and-drink', + 'travel-and-places', + 'activities', + 'objects', + 'symbols', + 'flags', +] + +const REGIONAL_INDICATORS = (() => { + const start = 0x1f1e6 + const end = 0x1f1ff + const A = 'A'.codePointAt(0) + const res = new Array(end - start + 1) + for (let i = start; i <= end; ++i) { + const letter = String.fromCodePoint(A + i - start) + res[i - start] = { + replacement: String.fromCodePoint(i), + imageUrl: false, + displayText: 'regional_indicator_' + letter, + displayTextI18n: { + key: 'emoji.regional_indicator', + args: { letter }, + }, + } + } + return res +})() + +const REMOTE_INTERACTION_URL = '/main/ostatus' + +const defaultState = { + // Stuff from apiConfig + name: 'Pleroma FE', + registrationOpen: true, + server: 'http://localhost:4040/', + textlimit: 5000, + bannerlimit: null, + avatarlimit: null, + backgroundlimit: null, + uploadlimit: null, + fieldsLimits: null, + private: false, + federating: true, + federationPolicy: null, + themesIndex: null, + stylesIndex: null, + palettesIndex: null, + themeData: null, // used for theme editor v2 + vapidPublicKey: null, + + // Stuff from static/config.json + loginMethod: 'password', + disableUpdateNotification: false, + + // Instance-wide configurations that should not be changed by individual users + instanceIdentity: { + ...staticOrApiConfigDefault, + }, + + // Instance admins can override default settings for the whole instance + prefsStorage: { + ...instanceDefaultConfig, + }, + + // Custom emoji from server + customEmoji: [], + customEmojiFetched: false, + + // Unicode emoji from bundle + emoji: {}, + emojiFetched: false, + unicodeEmojiAnnotations: {}, + + // Known domains list for user's domain-muting + knownDomains: [], + + // Moderation stuff + staffAccounts: [], + accountActivationRequired: null, + accountApprovalRequired: null, + birthdayRequired: false, + birthdayMinAge: 0, + restrictedNicknames: [], + + // Feature-set, apparently, not everything here is reported... + featureSet: { + postFormats: [], + mailerEnabled: false, + safeDM: true, + shoutAvailable: false, + pleromaExtensionsAvailable: true, + pleromaChatMessagesAvailable: false, + pleromaCustomEmojiReactionsAvailable: false, + pleromaBookmarkFoldersAvailable: false, + pleromaPublicFavouritesAvailable: true, + statusNotificationTypeAvailable: true, + gopherAvailable: false, + editingAvailable: false, + mediaProxyAvailable: false, + suggestionsEnabled: false, + suggestionsWeb: '', + quotingAvailable: false, + groupActorAvailable: false, + blockExpiration: false, + tagPolicyAvailable: false, + pollsAvailable: false, + localBubbleInstances: [], // Akkoma + }, + + // Html stuff + instanceSpecificPanelContent: '', + tos: '', + + // Version Information + backendVersion: '', + backendRepository: '', + frontendVersion: '', + + pollsAvailable: false, + pollLimits: { + max_options: 4, + max_option_chars: 255, + min_expiration: 60, + max_expiration: 60 * 60 * 24, + }, +} + +const loadAnnotations = (lang) => { + return annotationsLoader[lang]().then((k) => k.default) +} + +const injectAnnotations = (emoji, annotations) => { + const availableLangs = Object.keys(annotations) + + return { + ...emoji, + annotations: availableLangs.reduce((acc, cur) => { + acc[cur] = annotations[cur][emoji.replacement] + return acc + }, {}), + } +} + +const injectRegionalIndicators = (groups) => { + groups.symbols.push(...REGIONAL_INDICATORS) + return groups +} + +export const useInstanceStore = defineStore('instance', { + state: () => ({ ...defaultState }), + getters: { + instanceDefaultConfig(state) { + return instanceDefaultProperties + .map((key) => [key, state[key]]) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) + }, + groupedCustomEmojis(state) { + const packsOf = (emoji) => { + const packs = emoji.tags + .filter((k) => k.startsWith('pack:')) + .map((k) => { + const packName = k.slice(5) // remove 'pack:' prefix + return { + id: `custom-${packName}`, + text: packName, + } + }) + + if (!packs.length) { + return [ + { + id: 'unpacked', + }, + ] + } else { + return packs + } + } + + return this.customEmoji.reduce((res, emoji) => { + packsOf(emoji).forEach(({ id: packId, text: packName }) => { + if (!res[packId]) { + res[packId] = { + id: packId, + text: packName, + image: emoji.imageUrl, + emojis: [], + } + } + res[packId].emojis.push(emoji) + }) + return res + }, {}) + }, + standardEmojiList(state) { + return SORTED_EMOJI_GROUP_IDS.map((groupId) => + (this.emoji[groupId] || []).map((k) => + injectAnnotations(k, this.unicodeEmojiAnnotations), + ), + ).reduce((a, b) => a.concat(b), []) + }, + standardEmojiGroupList(state) { + return SORTED_EMOJI_GROUP_IDS.map((groupId) => ({ + id: groupId, + emojis: (this.emoji[groupId] || []).map((k) => + injectAnnotations(k, this.unicodeEmojiAnnotations), + ), + })) + }, + instanceDomain(state) { + return new URL(this.server).hostname + }, + remoteInteractionLink(state) { + const server = this.server.endsWith('/') + ? this.server.slice(0, -1) + : this.server + const link = server + REMOTE_INTERACTION_URL + + return ({ statusId, nickname }) => { + if (statusId) { + return `${link}?status_id=${statusId}` + } else { + return `${link}?nickname=${nickname}` + } + } + }, + }, + actions: { + set({ path, value }) { + if (get(defaultState, path) === undefined) + console.error(`Unknown instance option ${path}, value: ${value}`) + set(this, path, value) + switch (name) { + case 'name': + useInterfaceStore().setPageTitle() + break + case 'shoutAvailable': + if (value) { + window.vuex.dispatch('initializeSocket') + } + break + } + }, + async getStaticEmoji() { + try { + // See build/emojis_plugin for more details + const values = (await import('/src/assets/emoji.json')).default + + const emoji = Object.keys(values).reduce((res, groupId) => { + res[groupId] = values[groupId].map((e) => ({ + displayText: e.slug, + imageUrl: false, + replacement: e.emoji, + })) + return res + }, {}) + this.emoji = injectRegionalIndicators(emoji) + } catch (e) { + console.warn("Can't load static emoji\n", e) + } + }, + + loadUnicodeEmojiData(language) { + const langList = ensureFinalFallback(language) + + return Promise.all( + langList.map(async (lang) => { + if (!this.unicodeEmojiAnnotations[lang]) { + try { + const annotations = await loadAnnotations(lang) + this.unicodeEmojiAnnotations[lang] = annotations + } catch (e) { + console.warn( + `Error loading unicode emoji annotations for ${lang}: `, + e, + ) + // ignore + } + } + }), + ) + }, + + async getCustomEmoji() { + try { + let res = await window.fetch('/api/v1/pleroma/emoji') + if (!res.ok) { + res = await window.fetch('/api/pleroma/emoji.json') + } + if (res.ok) { + const result = await res.json() + const values = Array.isArray(result) + ? Object.assign({}, ...result) + : result + const caseInsensitiveStrCmp = (a, b) => { + const la = a.toLowerCase() + const lb = b.toLowerCase() + return la > lb ? 1 : la < lb ? -1 : 0 + } + const noPackLast = (a, b) => { + const aNull = a === '' + const bNull = b === '' + if (aNull === bNull) { + return 0 + } else if (aNull && !bNull) { + return 1 + } else { + return -1 + } + } + const byPackThenByName = (a, b) => { + const packOf = (emoji) => + (emoji.tags.filter((k) => k.startsWith('pack:'))[0] || '').slice( + 5, + ) + const packOfA = packOf(a) + const packOfB = packOf(b) + return ( + noPackLast(packOfA, packOfB) || + caseInsensitiveStrCmp(packOfA, packOfB) || + caseInsensitiveStrCmp(a.displayText, b.displayText) + ) + } + + const emoji = Object.entries(values) + .map(([key, value]) => { + const imageUrl = value.image_url + return { + displayText: key, + imageUrl: imageUrl ? this.server + imageUrl : value, + tags: imageUrl + ? value.tags.sort((a, b) => (a > b ? 1 : 0)) + : ['utf'], + replacement: `:${key}: `, + } + // Technically could use tags but those are kinda useless right now, + // should have been "pack" field, that would be more useful + }) + .sort(byPackThenByName) + this.customEmoji = emoji + } else { + throw res + } + } catch (e) { + console.warn("Can't load custom emojis\n", e) + } + }, + fetchEmoji() { + if (!this.customEmojiFetched) { + this.customEmojiFetched = true + window.vuex.dispatch('getCustomEmoji') + } + if (!this.emojiFetched) { + this.emojiFetched = true + window.vuex.dispatch('getStaticEmoji') + } + }, + + async getKnownDomains() { + try { + this.knownDomains = await apiService.fetchKnownDomains({ + credentials: window.vuex.state.users.currentUser.credentials, + }) + } catch (e) { + console.warn("Can't load known domains\n", e) + } + }, + }, +}) diff --git a/src/stores/serverSideStorage.js b/src/stores/serverSideStorage.js index 23c027b1e..73bf795cf 100644 --- a/src/stores/serverSideStorage.js +++ b/src/stores/serverSideStorage.js @@ -20,8 +20,9 @@ import { defaultState as configDefaultState, instanceDefaultConfig, } from 'src/modules/default_config_state' +import { useInstanceStore } from 'src/stores/instance' -export const VERSION = 1 +export const VERSION = 2 export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically export const COMMAND_TRIM_FLAGS = 1000 @@ -45,11 +46,7 @@ export const defaultState = { dontShowUpdateNotifs: false, collapseNav: false, muteFilters: {}, - ...{ - // reverting all the undefined to their initial values - ...configDefaultState, - ...instanceDefaultConfig, - }, + ...configDefaultState, }, collections: { pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'], @@ -377,21 +374,21 @@ export const _resetFlags = ( return result } -export const _doMigrations = (cache) => { - if (!cache) return cache +export const _doMigrations = (cache, live) => { + const data = cache ?? live - if (cache._version < VERSION) { + if (data._version < VERSION) { console.debug( - 'Local cached data has older version, seeing if there any migrations that can be applied', + 'Data has older version, seeing if there any migrations that can be applied', ) // no migrations right now since we only have one version console.debug('No migrations found') } - if (cache._version > VERSION) { + if (data._version > VERSION) { console.debug( - 'Local cached data has newer version, seeing if there any reverse migrations that can be applied', + 'Data has newer version, seeing if there any reverse migrations that can be applied', ) // no reverse migrations right now but we leave a possibility of loading a hotpatch if need be @@ -401,8 +398,8 @@ export const _doMigrations = (cache) => { return window._PLEROMA_HOTPATCH.reverseMigrations.call( {}, 'serverSideStorage', - { from: cache._version, to: VERSION }, - cache, + { from: data._version, to: VERSION }, + data, ) } } @@ -580,7 +577,7 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { cache = null } - cache = _doMigrations(cache) + cache = _doMigrations(cache, live) let { recent, stale, needUpload } = _getRecentData(cache, live) @@ -649,6 +646,12 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { }) }, }, + getters: { + mergedConfig: (state) => ({ + ...useInstanceStore().prefsStorage, + ...state.prefsStorage.simple, + }), + }, persist: { afterLoad(state) { return state From dc7308766cf9347d4335730e17680fa5c9c2d9c8 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 22 Jan 2026 00:55:55 +0200 Subject: [PATCH 05/29] fix some navigation issues --- src/boot/routes.js | 4 +++- src/components/nav_panel/nav_panel.js | 18 +++++++++++------- src/components/navigation/navigation_pins.js | 16 +++++++++------- src/components/timeline_menu/timeline_menu.js | 13 ++++++++----- src/modules/api.js | 3 ++- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/boot/routes.js b/src/boot/routes.js index 3296755a1..4077f67ba 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -32,6 +32,8 @@ import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_fold import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue' import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue' +import { useInstanceStore } from 'src/stores/instance.js' + export default (store) => { const validateAuthenticatedRoute = (to, from, next) => { if (store.state.users.currentUser) { @@ -200,7 +202,7 @@ export default (store) => { }, ] - if (store.state.instance.pleromaChatMessagesAvailable) { + if (useInstanceStore().featureSet.pleromaChatMessagesAvailable) { routes = routes.concat([ { name: 'chat', diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index d80cd0a5d..36b6fe95d 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -8,8 +8,9 @@ import { filterNavigation } from 'src/components/navigation/filter.js' import { ROOT_ITEMS, TIMELINES } from 'src/components/navigation/navigation.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue' -import { useAnnouncementsStore } from 'src/stores/announcements' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useAnnouncementsStore } from 'src/stores/announcements.js' +import { useInstanceStore } from 'src/stores/instance.js' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -115,16 +116,19 @@ const NavPanel = { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), + ...mapPiniaState(useInstanceStore, { + bubbleTimeline: (store) => + store.featureSet.localBubbleInstances.length > 0, + pleromaChatMessagesAvailable: (store) => + store.featureSet.pleromaChatMessagesAvailable, + bookmarkFolders: (store) => + store.featureSet.pleromaBookmarkFoldersAvailable, + }), ...mapState({ currentUser: (state) => state.users.currentUser, followRequestCount: (state) => state.api.followRequests.length, privateMode: (state) => state.instance.private, federating: (state) => state.instance.federating, - pleromaChatMessagesAvailable: (state) => - state.instance.pleromaChatMessagesAvailable, - bookmarkFolders: (state) => - state.instance.pleromaBookmarkFoldersAvailable, - bubbleTimeline: (state) => state.instance.localBubbleInstances.length > 0, }), timelinesItems() { return filterNavigation( diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js index e14edce14..c4979086f 100644 --- a/src/components/navigation/navigation_pins.js +++ b/src/components/navigation/navigation_pins.js @@ -15,6 +15,7 @@ import StillImage from 'src/components/still-image/still-image.vue' import { useAnnouncementsStore } from 'src/stores/announcements' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' import { useListsStore } from 'src/stores/lists' +import { useInstanceStore } from 'src/stores/instance' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { library } from '@fortawesome/fontawesome-svg-core' @@ -71,14 +72,15 @@ const NavPanel = { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), + ...mapPiniaState(useInstanceStore, { + privateMode: (store) => store.featureSet.private, + federating: (store) => store.featureSet.federating, + pleromaChatMessagesAvailable: (store) => store.featureSet.pleromaChatMessagesAvailable, + bubbleTimelinesSupported: (store) => store.featureSet.localBubbleInstances.length > 0, + }), ...mapState({ currentUser: (state) => state.users.currentUser, followRequestCount: (state) => state.api.followRequests.length, - privateMode: (state) => state.instance.private, - federating: (state) => state.instance.federating, - pleromaChatMessagesAvailable: (state) => - state.instance.pleromaChatMessagesAvailable, - bubbleTimeline: (state) => state.instance.localBubbleInstances.length > 0, }), pinnedList() { if (!this.currentUser) { @@ -94,7 +96,7 @@ const NavPanel = { isFederating: this.federating, isPrivate: this.privateMode, currentUser: this.currentUser, - supportsBubbleTimeline: this.bubbleTimeline, + supportsBubbleTimeline: this.bubbleTimelinesSupported, supportsBookmarkFolders: this.bookmarks, }, ) @@ -113,7 +115,7 @@ const NavPanel = { { hasChats: this.pleromaChatMessagesAvailable, hasAnnouncements: this.supportsAnnouncements, - supportsBubbleTimeline: this.bubbleTimeline, + supportsBubbleTimeline: this.bubbleTimelinesSupported, supportsBookmarkFolders: this.bookmarks, isFederating: this.federating, isPrivate: this.privateMode, diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index 97c4dbe35..417aa19a4 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -1,9 +1,11 @@ +import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'vuex' import { filterNavigation } from 'src/components/navigation/filter.js' import { TIMELINES } from 'src/components/navigation/navigation.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' +import { useInstanceStore } from 'src/stores/instance.js' import { useInterfaceStore } from 'src/stores/interface' import { useListsStore } from 'src/stores/lists' import BookmarkFoldersMenuContent from '../bookmark_folders_menu/bookmark_folders_menu_content.vue' @@ -60,11 +62,12 @@ const TimelineMenu = { }, ...mapState({ currentUser: (state) => state.users.currentUser, - privateMode: (state) => state.instance.private, - federating: (state) => state.instance.federating, - bookmarkFolders: (state) => - state.instance.pleromaBookmarkFoldersAvailable, - bubbleTimeline: (state) => state.instance.localBubbleInstances.length > 0, + }), + ...mapPiniaState(useInstanceStore, { + bookmarkFolders: (store) => store.featureSet.pleromaBookmarkFoldersAvailable, + bubbleTimeline: (state) => store.featureSet.localBubbleInstances.length > 0, + privateMode: (state) => store.private, + federating: (state) => store.federating, }), timelinesList() { return filterNavigation( diff --git a/src/modules/api.js b/src/modules/api.js index cb8b72e10..4410d0902 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,5 +1,6 @@ import { Socket } from 'phoenix' +import { useInstanceStore } from 'src/stores/instance.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useShoutStore } from 'src/stores/shout.js' import { WSConnectionStatus } from '../services/api/api.service.js' @@ -322,7 +323,7 @@ const api = { // Bookmark folders startFetchingBookmarkFolders(store) { if (store.state.fetchers.bookmarkFolders) return - if (!store.rootState.instance.pleromaBookmarkFoldersAvailable) return + if (!useInstanceStore().featureSet.pleromaBookmarkFoldersAvailable) return const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store }) store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher }) From 5bdf3415606f84f4598c77d51a49aa56052f3f25 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 22 Jan 2026 17:16:51 +0200 Subject: [PATCH 06/29] MASSIVE refactor, replacing instance module with store, separating emoji stuff into its own store, making sure everything refers to new stores (WIP) --- src/App.js | 27 +- src/boot/after_store.js | 1 + src/boot/routes.js | 3 +- src/components/about/about.js | 10 +- .../account_actions/account_actions.js | 5 +- src/components/attachment/attachment.js | 12 +- src/components/avatar_list/avatar_list.js | 3 +- .../basic_user_card/basic_user_card.js | 3 +- src/components/conversation/conversation.js | 12 +- src/components/desktop_nav/desktop_nav.js | 64 +++-- src/components/dialog_modal/dialog_modal.js | 4 +- src/components/draft/draft.js | 3 +- src/components/draft_closer/draft_closer.js | 5 +- src/components/emoji_input/emoji_input.js | 10 +- src/components/emoji_input/suggestor.js | 4 +- src/components/emoji_picker/emoji_picker.js | 19 +- .../extra_notifications.js | 12 +- .../features_panel/features_panel.js | 17 +- src/components/follow_button/follow_button.js | 3 +- .../follow_request_card.js | 3 +- .../instance_specific_panel.js | 4 +- src/components/media_upload/media_upload.js | 7 +- src/components/mention_link/mention_link.js | 29 +-- src/components/mobile_nav/mobile_nav.js | 13 +- .../mobile_post_status_button.js | 5 +- .../moderation_tools/moderation_tools.js | 3 +- src/components/nav_panel/nav_panel.js | 18 +- src/components/navigation/navigation_entry.js | 10 +- src/components/navigation/navigation_pins.js | 12 +- src/components/notification/notification.js | 8 +- .../notifications/notification_filters.vue | 3 +- src/components/notifications/notifications.js | 12 +- .../oauth_callback/oauth_callback.js | 3 +- src/components/poll/poll.js | 3 +- src/components/poll/poll_form.js | 3 +- .../post_status_form/post_status_form.js | 56 ++-- .../remove_follower_button.js | 3 +- src/components/report/report.js | 3 +- .../settings_modal/admin_tabs/emoji_tab.js | 3 +- .../helpers/attachment_setting.js | 5 +- .../helpers/pwa_manifest_icons_setting.js | 5 +- .../settings_modal/helpers/setting.js | 12 +- .../helpers/shared_computed_object.js | 4 +- .../settings_modal/tabs/appearance_tab.js | 25 +- .../settings_modal/tabs/clutter_tab.js | 21 +- .../settings_modal/tabs/composing_tab.js | 6 +- .../settings_modal/tabs/filtering_tab.js | 25 +- .../settings_modal/tabs/general_tab.js | 4 +- .../settings_modal/tabs/layout_tab.js | 10 +- .../tabs/mutes_and_blocks_tab.js | 4 +- .../tabs/old_theme_tab/old_theme_tab.js | 6 +- .../tabs/security_tab/security_tab.js | 3 +- src/components/shout_panel/shout_panel.js | 3 +- src/components/side_drawer/side_drawer.js | 13 +- src/components/staff_panel/staff_panel.js | 3 +- src/components/status/status.js | 11 +- .../status_action_buttons/action_button.js | 3 +- .../status_action_buttons.js | 12 +- src/components/status_body/status_body.js | 4 +- .../status_content/status_content.js | 6 +- .../sticker_picker/sticker_picker.js | 4 +- .../still-image/still-image-emoji-popover.vue | 5 +- src/components/still-image/still-image.js | 4 +- .../terms_of_service_panel.js | 16 +- src/components/timeago/timeago.vue | 7 +- src/components/timeline/timeline.js | 10 +- src/components/timeline_menu/timeline_menu.js | 10 +- .../update_notification.js | 19 +- src/components/user_avatar/user_avatar.js | 3 +- src/components/user_card/user_card.js | 29 ++- src/components/user_link/user_link.vue | 3 +- src/components/user_popover/user_popover.js | 15 +- src/components/user_profile/user_profile.js | 4 +- .../user_timed_filter_modal.js | 9 +- .../video_attachment/video_attachment.js | 6 +- src/components/who_to_follow/who_to_follow.js | 1 + .../who_to_follow_panel.js | 9 +- src/lib/language_plugin.js | 7 + src/lib/push_notifications_plugin.js | 61 +++-- src/main.js | 10 +- src/modules/api.js | 2 +- src/modules/config.js | 62 ----- src/modules/index.js | 2 - src/modules/instance.js | 66 +---- src/modules/notifications.js | 6 +- src/modules/users.js | 33 +-- .../notification_utils/notification_utils.js | 12 +- .../notifications_fetcher.service.js | 7 +- .../timeline_fetcher.service.js | 4 +- src/stores/emoji.js | 243 ++++++++++++++++++ src/stores/instance.js | 234 +---------------- src/stores/interface.js | 67 ++--- .../{serverSideStorage.js => sync_config.js} | 12 +- src/sw.js | 9 +- ...ideStorage.spec.js => sync_config.spec.js} | 40 +-- 95 files changed, 801 insertions(+), 833 deletions(-) create mode 100644 src/lib/language_plugin.js create mode 100644 src/stores/emoji.js rename src/stores/{serverSideStorage.js => sync_config.js} (98%) rename test/unit/specs/modules/{serverSideStorage.spec.js => sync_config.spec.js} (94%) diff --git a/src/App.js b/src/App.js index bfdeb2be4..ae34d9d5e 100644 --- a/src/App.js +++ b/src/App.js @@ -3,7 +3,7 @@ import { mapState } from 'pinia' import { defineAsyncComponent } from 'vue' import { mapGetters } from 'vuex' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import DesktopNav from './components/desktop_nav/desktop_nav.vue' import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue' import FeaturesPanel from './components/features_panel/features_panel.vue' @@ -22,9 +22,9 @@ import UserReportingModal from './components/user_reporting_modal/user_reporting import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import { getOrCreateServiceWorker } from './services/sw/sw' import { windowHeight, windowWidth } from './services/window_utils/window_utils' -import { useInstanceStore } from './stores/instance' -import { useInterfaceStore } from './stores/interface' -import { useShoutStore } from './stores/shout' +import { useInstanceStore } from 'src/stores/instance.js' +import { useInterfaceStore } from 'src/stores/interface.js' +import { useShoutStore } from 'src/stores/shout.js' export default { name: 'app', @@ -70,9 +70,6 @@ export default { }, }, created() { - // Load the locale from the storage - const val = this.$store.getters.mergedConfig.interfaceLanguage - this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) document.getElementById('modal').classList = ['-' + this.layoutType] // Create bound handlers @@ -124,7 +121,7 @@ export default { ] }, navClasses() { - const { navbarColumnStretch } = this.$store.getters.mergedConfig + const { navbarColumnStretch } = useSyncConfigStore().mergedConfig return [ '-' + this.layoutType, ...(navbarColumnStretch ? ['-column-stretch'] : []), @@ -159,19 +156,19 @@ export default { if (this.isChats) return false if (this.isListEdit) return false return ( - this.$store.getters.mergedConfig.alwaysShowNewPostButton || + useSyncConfigStore().mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile' ) }, shoutboxPosition() { - return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false + return useSyncConfigStore().mergedConfig.alwaysShowNewPostButton || false }, layoutType() { return useInterfaceStore().layoutType }, reverseLayout() { const { thirdColumnMode, sidebarRight: reverseSetting } = - this.$store.getters.mergedConfig + useSyncConfigStore().mergedConfig if (this.layoutType !== 'wide') { return reverseSetting } else { @@ -181,16 +178,16 @@ export default { } }, noSticky() { - return this.$store.getters.mergedConfig.disableStickyHeaders + return useSyncConfigStore().mergedConfig.disableStickyHeaders }, showScrollbars() { - return this.$store.getters.mergedConfig.showScrollbars + return useSyncConfigStore().mergedConfig.showScrollbars }, scrollParent() { return window /* this.$refs.appContentRef */ }, ...mapGetters(['mergedConfig']), - ...mapState(useServerSideStorageStore, { + ...mapState(useSyncConfigStore, { hideShoutbox: (store) => store.mergedConfig.hideShoutbox, }), ...mapState(useInstanceStore, { @@ -198,7 +195,7 @@ export default { this.mergedConfig.hideInstanceWallpaper ? null : store.background, showInstanceSpecificPanel: (store) => store.showInstanceSpecificPanel && - !this.$store.getters.mergedConfig.hideISP && + !useSyncConfigStore().mergedConfig.hideISP && store.instanceSpecificPanelContent, }), ...mapState(useInstanceStore, [ diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 48dc42bc1..e89e3abcb 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -542,6 +542,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { ? overrides.target : window.location.origin useInstanceStore().set({ path: 'server', value: server }) + console.log('AFTER', useInstanceStore().server, server) await setConfig({ store }) try { diff --git a/src/boot/routes.js b/src/boot/routes.js index 4077f67ba..0b8df95ac 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -28,12 +28,11 @@ import UserProfile from 'components/user_profile/user_profile.vue' import WhoToFollow from 'components/who_to_follow/who_to_follow.vue' import NavPanel from 'src/components/nav_panel/nav_panel.vue' +import { useInstanceStore } from 'src/stores/instance.js' import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue' import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue' import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue' -import { useInstanceStore } from 'src/stores/instance.js' - export default (store) => { const validateAuthenticatedRoute = (to, from, next) => { if (store.state.users.currentUser) { diff --git a/src/components/about/about.js b/src/components/about/about.js index d091dfd0a..4c1ec87d1 100644 --- a/src/components/about/about.js +++ b/src/components/about/about.js @@ -1,3 +1,5 @@ +import { useInstanceStore } from 'src/stores/instance.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import FeaturesPanel from '../features_panel/features_panel.vue' import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue' import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue' @@ -14,13 +16,13 @@ const About = { }, computed: { showFeaturesPanel() { - return this.$store.state.instance.showFeaturesPanel + return useInstanceStore().showFeaturesPanel }, showInstanceSpecificPanel() { return ( - this.$store.state.instance.showInstanceSpecificPanel && - !this.$store.getters.mergedConfig.hideISP && - this.$store.state.instance.instanceSpecificPanelContent + useInstanceStore().showInstanceSpecificPanel && + !useSyncConfigStore().mergedConfig.hideISP && + useInstanceStore().instanceSpecificPanelContent ) }, }, diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index ee94dc544..f9a1abf86 100644 --- a/src/components/account_actions/account_actions.js +++ b/src/components/account_actions/account_actions.js @@ -3,6 +3,7 @@ import { mapState } from 'vuex' import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue' import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue' import { useReportsStore } from 'src/stores/reports' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import ConfirmModal from '../confirm_modal/confirm_modal.vue' import Popover from '../popover/popover.vue' import ProgressButton from '../progress_button/progress_button.vue' @@ -87,10 +88,10 @@ const AccountActions = { }, computed: { shouldConfirmBlock() { - return this.$store.getters.mergedConfig.modalOnBlock + return useSyncConfigStore().mergedConfig.modalOnBlock }, shouldConfirmRemoveUserFromFollowers() { - return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers + return useSyncConfigStore().mergedConfig.modalOnRemoveUserFromFollowers }, ...mapState({ blockExpirationSupported: (state) => state.instance.blockExpiration, diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 31fceba60..2b3211cb3 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -1,6 +1,8 @@ import { mapGetters } from 'vuex' +import { useInstanceStore } from 'src/stores/instance.js' import { useMediaViewerStore } from 'src/stores/media_viewer' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import nsfwImage from '../../assets/nsfw.png' import fileTypeService from '../../services/file_type/file_type.service.js' import Flash from '../flash/flash.vue' @@ -53,9 +55,9 @@ const Attachment = { data() { return { localDescription: this.description || this.attachment.description, - nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage, - hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw, - preloadImage: this.$store.getters.mergedConfig.preloadImage, + nsfwImage: useInstanceStore().nsfwCensorImage || nsfwImage, + hideNsfwLocal: useSyncConfigStore().mergedConfig.hideNsfw, + preloadImage: useSyncConfigStore().mergedConfig.preloadImage, loading: false, img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && @@ -89,7 +91,7 @@ const Attachment = { return this.size === 'hide' }, useContainFit() { - return this.$store.getters.mergedConfig.useContainFit + return useSyncConfigStore().mergedConfig.useContainFit }, placeholderName() { if (this.attachment.description === '' || !this.attachment.description) { @@ -104,7 +106,7 @@ const Attachment = { return 'file' }, referrerpolicy() { - return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer' + return useInstanceStore().mediaProxyAvailable ? '' : 'no-referrer' }, type() { return fileTypeService.fileType(this.attachment.mimetype) diff --git a/src/components/avatar_list/avatar_list.js b/src/components/avatar_list/avatar_list.js index aad157686..938713207 100644 --- a/src/components/avatar_list/avatar_list.js +++ b/src/components/avatar_list/avatar_list.js @@ -1,4 +1,5 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { useInstanceStore } from 'src/stores/instance.js' import UserAvatar from '../user_avatar/user_avatar.vue' const AvatarList = { @@ -16,7 +17,7 @@ const AvatarList = { return generateProfileLink( user.id, user.screen_name, - this.$store.state.instance.restrictedNicknames, + useInstanceStore().restrictedNicknames, ) }, }, diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js index cc4cbce44..8d2b17b7d 100644 --- a/src/components/basic_user_card/basic_user_card.js +++ b/src/components/basic_user_card/basic_user_card.js @@ -1,5 +1,6 @@ import RichContent from 'src/components/rich_content/rich_content.jsx' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { useInstanceStore } from 'src/stores/instance.js' import UserAvatar from '../user_avatar/user_avatar.vue' import UserLink from '../user_link/user_link.vue' import UserPopover from '../user_popover/user_popover.vue' @@ -17,7 +18,7 @@ const BasicUserCard = { return generateProfileLink( user.id, user.screen_name, - this.$store.state.instance.restrictedNicknames, + useInstanceStore().restrictedNicknames, ) }, }, diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 3caf4add7..fe654cba2 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -2,7 +2,9 @@ import { clone, filter, findIndex, get, reduce } from 'lodash' import { mapState as mapPiniaState } from 'pinia' import { mapGetters, mapState } from 'vuex' +import { useInstanceStore } from 'src/stores/instance.js' import { useInterfaceStore } from 'src/stores/interface' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { WSConnectionStatus } from '../../services/api/api.service.js' import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue' import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue' @@ -80,7 +82,7 @@ const conversation = { // maxDepthInThread = max number of depths that is *visible* // since our depth starts with 0 and "showing" means "showing children" // there is a -2 here - const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2 + const maxDepth = useSyncConfigStore().mergedConfig.maxDepthInThread - 2 return maxDepth >= 1 ? maxDepth : 1 }, streamingEnabled() { @@ -90,22 +92,22 @@ const conversation = { ) }, displayStyle() { - return this.$store.getters.mergedConfig.conversationDisplay + return useSyncConfigStore().mergedConfig.conversationDisplay }, isTreeView() { return !this.isLinearView }, treeViewIsSimple() { - return !this.$store.getters.mergedConfig.conversationTreeAdvanced + return !useSyncConfigStore().mergedConfig.conversationTreeAdvanced }, isLinearView() { return this.displayStyle === 'linear' }, shouldFadeAncestors() { - return this.$store.getters.mergedConfig.conversationTreeFadeAncestors + return useSyncConfigStore().mergedConfig.conversationTreeFadeAncestors }, otherRepliesButtonPosition() { - return this.$store.getters.mergedConfig.conversationOtherRepliesButton + return useSyncConfigStore().mergedConfig.conversationOtherRepliesButton }, showOtherRepliesButtonBelowStatus() { return this.otherRepliesButtonPosition === 'below' diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index 8ab54b3df..df7078afd 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -1,6 +1,9 @@ import SearchBar from 'components/search_bar/search_bar.vue' +import { mapState } from 'pinia' -import { useInterfaceStore } from 'src/stores/interface' +import { useInstanceStore } from 'src/stores/instance.js' +import { useInterfaceStore } from 'src/stores/interface.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { library } from '@fortawesome/fontawesome-svg-core' @@ -31,6 +34,14 @@ library.add( faCog, faInfoCircle, ) +const supportsMask = + window.CSS && + window.CSS.supports && + (window.CSS.supports('mask-size', 'contain') || + window.CSS.supports('-webkit-mask-size', 'contain') || + window.CSS.supports('-moz-mask-size', 'contain') || + window.CSS.supports('-ms-mask-size', 'contain') || + window.CSS.supports('-o-mask-size', 'contain')) export default { components: { @@ -39,68 +50,51 @@ export default { }, data: () => ({ searchBarHidden: true, - supportsMask: - window.CSS && - window.CSS.supports && - (window.CSS.supports('mask-size', 'contain') || - window.CSS.supports('-webkit-mask-size', 'contain') || - window.CSS.supports('-moz-mask-size', 'contain') || - window.CSS.supports('-ms-mask-size', 'contain') || - window.CSS.supports('-o-mask-size', 'contain')), showingConfirmLogout: false, }), computed: { - enableMask() { - return this.supportsMask && this.$store.state.instance.logoMask - }, logoStyle() { return { - visibility: this.enableMask ? 'hidden' : 'visible', + visibility: this.logoMask ? 'hidden' : 'visible', } }, logoMaskStyle() { - return this.enableMask + return this.logoMask ? { - 'mask-image': `url(${this.$store.state.instance.logo})`, + 'mask-image': `url(${this.logo})`, } : { - 'background-color': this.enableMask ? '' : 'transparent', + 'background-color': this.logoMask ? '' : 'transparent', } }, logoBgStyle() { return Object.assign( { - margin: `${this.$store.state.instance.logoMargin} 0`, + margin: `${this.logoMargin} 0`, opacity: this.searchBarHidden ? 1 : 0, }, - this.enableMask + this.logoMask ? {} : { - 'background-color': this.enableMask ? '' : 'transparent', + 'background-color': this.logoMask ? '' : 'transparent', }, ) }, - logo() { - return this.$store.state.instance.logo - }, - sitename() { - return this.$store.state.instance.name - }, - hideSitename() { - return this.$store.state.instance.hideSitename - }, - logoLeft() { - return this.$store.state.instance.logoLeft - }, currentUser() { return this.$store.state.users.currentUser }, - privateMode() { - return this.$store.state.instance.private - }, shouldConfirmLogout() { - return this.$store.getters.mergedConfig.modalOnLogout + return useSyncConfigStore().mergedConfig.modalOnLogout }, + ...mapState(useInstanceStore, { + logo: (state) => state.instanceIdentity.logo, + logoMask: (state) => supportsMask && state.instanceIdentity.logoMask, + logoLeft: (state) => state.instanceIdentity.logoLeft, + logoMargin: (state) => state.instanceIdentity.logoMargin, + sitename: (state) => state.instanceIdentity.name, + privateMode: (state) => state.private, + hideSitename: (state) => state.instanceIdentity.hideSitename, + }), }, methods: { scrollToTop() { diff --git a/src/components/dialog_modal/dialog_modal.js b/src/components/dialog_modal/dialog_modal.js index 8070d3429..e5c399086 100644 --- a/src/components/dialog_modal/dialog_modal.js +++ b/src/components/dialog_modal/dialog_modal.js @@ -1,3 +1,5 @@ +import { useSyncConfigStore } from 'src/stores/sync_config.js' + const DialogModal = { props: { darkOverlay: { @@ -13,7 +15,7 @@ const DialogModal = { }, computed: { mobileCenter() { - return this.$store.getters.mergedConfig.modalMobileCenter + return useSyncConfigStore().mergedConfig.modalMobileCenter }, }, } diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js index 971a75b10..5caaf7a96 100644 --- a/src/components/draft/draft.js +++ b/src/components/draft/draft.js @@ -5,6 +5,7 @@ import EditStatusForm from 'src/components/edit_status_form/edit_status_form.vue import Gallery from 'src/components/gallery/gallery.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' import StatusContent from 'src/components/status_content/status_content.vue' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faPollH } from '@fortawesome/free-solid-svg-icons' @@ -57,7 +58,7 @@ const Draft = { : undefined }, localCollapseSubjectDefault() { - return this.$store.getters.mergedConfig.collapseMessageWithSubject + return useSyncConfigStore().mergedConfig.collapseMessageWithSubject }, nsfwClickthrough() { if (!this.draft.nsfw) { diff --git a/src/components/draft_closer/draft_closer.js b/src/components/draft_closer/draft_closer.js index d724ab4ac..816ffd1ee 100644 --- a/src/components/draft_closer/draft_closer.js +++ b/src/components/draft_closer/draft_closer.js @@ -1,4 +1,5 @@ import DialogModal from 'src/components/dialog_modal/dialog_modal.vue' +import { useSyncConfigStore } from 'src/stores/sync_config.js' const DraftCloser = { data() { @@ -12,10 +13,10 @@ const DraftCloser = { emits: ['save', 'discard'], computed: { action() { - if (this.$store.getters.mergedConfig.autoSaveDraft) { + if (useSyncConfigStore().mergedConfig.autoSaveDraft) { return 'save' } else { - return this.$store.getters.mergedConfig.unsavedPostAction + return useSyncConfigStore().mergedConfig.unsavedPostAction } }, shouldConfirm() { diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 24794640e..a9c024993 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -2,7 +2,8 @@ import { take } from 'lodash' import Popover from 'src/components/popover/popover.vue' import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue' -import { ensureFinalFallback } from '../../i18n/languages.js' +import { ensureFinalFallback } from 'src/i18n/languages.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import Completion from '../../services/completion/completion.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import genRandomSeed from '../../services/random_seed/random_seed.service.js' @@ -131,10 +132,10 @@ const EmojiInput = { }, computed: { padEmoji() { - return this.$store.getters.mergedConfig.padEmoji + return useSyncConfigStore().mergedConfig.padEmoji }, defaultCandidateIndex() { - return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1 + return useSyncConfigStore().mergedConfig.autocompleteSelect ? 0 : -1 }, preText() { return this.modelValue.slice(0, this.caret) @@ -163,7 +164,7 @@ const EmojiInput = { }, languages() { return ensureFinalFallback( - this.$store.getters.mergedConfig.interfaceLanguage, + useSyncConfigStore().mergedConfig.interfaceLanguage, ) }, maybeLocalizedEmojiNamesAndKeywords() { @@ -331,7 +332,6 @@ const EmojiInput = { if (!this.pickerShown) { this.scrollIntoView() this.$refs.picker.showPicker() - this.$refs.picker.startEmojiLoad() } else { this.$refs.picker.hidePicker() } diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index 79d97cff7..c0d8f7ca1 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -1,8 +1,10 @@ +import { useEmojiStore } from 'src/stores/emoji.js' + /** * suggest - generates a suggestor function to be used by emoji-input * data: object providing source information for specific types of suggestions: * data.emoji - optional, an array of all emoji available i.e. - * (getters.standardEmojiList + state.instance.customEmoji) + * (useEmojiStore().standardEmojiList + state.instance.customEmoji) * data.users - optional, an array of all known users * updateUsersList - optional, a function to search and append to users * diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index e4221f706..2644d53e4 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -2,7 +2,10 @@ import { chunk, debounce, trim } from 'lodash' import { defineAsyncComponent } from 'vue' import Popover from 'src/components/popover/popover.vue' -import { ensureFinalFallback } from '../../i18n/languages.js' +import { ensureFinalFallback } from 'src/i18n/languages.js' +import { useInstanceStore } from 'src/stores/instance.js' +import { useEmojiStore } from 'src/stores/emoji.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import Checkbox from '../checkbox/checkbox.vue' import StillImage from '../still-image/still-image.vue' @@ -337,7 +340,7 @@ const EmojiPicker = { this.$nextTick(() => { this.updateEmojiSize() }) - return this.$store.getters.mergedConfig.fontSize + return useSyncConfigStore().mergedConfig.fontSize }, emojiHeight() { return this.emojiSize @@ -349,8 +352,8 @@ const EmojiPicker = { return this.showingStickers ? '' : this.activeGroup }, stickersAvailable() { - if (this.$store.state.instance.stickers) { - return this.$store.state.instance.stickers.length > 0 + if (useInstanceStore().stickers) { + return useInstanceStore().stickers.length > 0 } return 0 }, @@ -358,7 +361,7 @@ const EmojiPicker = { if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) { return {} } - const emojis = this.$store.getters.groupedCustomEmojis + const emojis = useEmojiStore().groupedCustomEmojis if (emojis.unpacked) { emojis.unpacked.text = this.$t('emoji.unpacked') } @@ -368,7 +371,7 @@ const EmojiPicker = { return Object.keys(this.allCustomGroups)[0] }, unicodeEmojiGroups() { - return this.$store.getters.standardEmojiGroupList.map((group) => ({ + return useEmojiStore().standardEmojiGroupList.map((group) => ({ id: `standard-${group.id}`, text: this.$t(`emoji.unicode_groups.${group.id}`), icon: UNICODE_EMOJI_GROUP_ICON[group.id], @@ -381,7 +384,7 @@ const EmojiPicker = { .concat(this.unicodeEmojiGroups) }, stickerPickerEnabled() { - return (this.$store.state.instance.stickers || []).length !== 0 + return (useInstanceStore().stickers || []).length !== 0 }, debouncedHandleKeywordChange() { return debounce(() => { @@ -402,7 +405,7 @@ const EmojiPicker = { }, languages() { return ensureFinalFallback( - this.$store.getters.mergedConfig.interfaceLanguage, + useSyncConfigStore().mergedConfig.interfaceLanguage, ) }, maybeLocalizedEmojiName() { diff --git a/src/components/extra_notifications/extra_notifications.js b/src/components/extra_notifications/extra_notifications.js index 1deebd878..59c6405ac 100644 --- a/src/components/extra_notifications/extra_notifications.js +++ b/src/components/extra_notifications/extra_notifications.js @@ -1,8 +1,9 @@ -import { mapState as mapPiniaState } from 'pinia' +import { mapState } from 'pinia' import { mapGetters } from 'vuex' -import { useAnnouncementsStore } from 'src/stores/announcements' -import { useInterfaceStore } from 'src/stores/interface' +import { useAnnouncementsStore } from 'src/stores/announcements.js' +import { useInterfaceStore } from 'src/stores/interface.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -51,10 +52,11 @@ const ExtraNotifications = { currentUser() { return this.$store.state.users.currentUser }, - ...mapGetters(['unreadChatCount', 'followRequestCount', 'mergedConfig']), - ...mapPiniaState(useAnnouncementsStore, { + ...mapGetters(['unreadChatCount', 'followRequestCount']), + ...mapState(useAnnouncementsStore, { unreadAnnouncementCount: 'unreadAnnouncementCount', }), + ...mapState(useSyncConfigStore, ['mergedConfig']), }, methods: { openNotificationSettings() { diff --git a/src/components/features_panel/features_panel.js b/src/components/features_panel/features_panel.js index e92cb975e..3b46e6f68 100644 --- a/src/components/features_panel/features_panel.js +++ b/src/components/features_panel/features_panel.js @@ -1,31 +1,32 @@ +import { useInstanceStore } from 'src/stores/instance.js' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' const FeaturesPanel = { computed: { shout: function () { - return this.$store.state.instance.shoutAvailable + return useInstanceStore().shoutAvailable }, pleromaChatMessages: function () { - return this.$store.state.instance.pleromaChatMessagesAvailable + return useInstanceStore().pleromaChatMessagesAvailable }, gopher: function () { - return this.$store.state.instance.gopherAvailable + return useInstanceStore().gopherAvailable }, whoToFollow: function () { - return this.$store.state.instance.suggestionsEnabled + return useInstanceStore().suggestionsEnabled }, mediaProxy: function () { - return this.$store.state.instance.mediaProxyAvailable + return useInstanceStore().mediaProxyAvailable }, minimalScopesMode: function () { - return this.$store.state.instance.minimalScopesMode + return useInstanceStore().minimalScopesMode }, textlimit: function () { - return this.$store.state.instance.textlimit + return useInstanceStore().textlimit }, uploadlimit: function () { return fileSizeFormatService.fileSizeFormat( - this.$store.state.instance.uploadlimit, + useInstanceStore().uploadlimit, ) }, }, diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js index eb545b28d..6c2186a89 100644 --- a/src/components/follow_button/follow_button.js +++ b/src/components/follow_button/follow_button.js @@ -1,3 +1,4 @@ +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { requestFollow, requestUnfollow, @@ -16,7 +17,7 @@ export default { }, computed: { shouldConfirmUnfollow() { - return this.$store.getters.mergedConfig.modalOnUnfollow + return useSyncConfigStore().mergedConfig.modalOnUnfollow }, isPressed() { return this.inProgress || this.relationship.following diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index c037ddf42..fa5487ec4 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -1,3 +1,4 @@ +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' import BasicUserCard from '../basic_user_card/basic_user_card.vue' import ConfirmModal from '../confirm_modal/confirm_modal.vue' @@ -76,7 +77,7 @@ const FollowRequestCard = { }, computed: { mergedConfig() { - return this.$store.getters.mergedConfig + return useSyncConfigStore().mergedConfig }, shouldConfirmApprove() { return this.mergedConfig.modalOnApproveFollow diff --git a/src/components/instance_specific_panel/instance_specific_panel.js b/src/components/instance_specific_panel/instance_specific_panel.js index eead52f40..c9e2e280f 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.js +++ b/src/components/instance_specific_panel/instance_specific_panel.js @@ -1,7 +1,9 @@ +import { useInstanceStore } from 'src/stores/instance.js' + const InstanceSpecificPanel = { computed: { instanceSpecificPanelContent() { - return this.$store.state.instance.instanceSpecificPanelContent + return useInstanceStore().instanceSpecificPanelContent }, }, } diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js index 8dc3d6c65..89561f3e3 100644 --- a/src/components/media_upload/media_upload.js +++ b/src/components/media_upload/media_upload.js @@ -1,5 +1,4 @@ -/* eslint-env browser */ - +import { useSyncConfigStore } from 'src/stores/sync_config.js' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import statusPosterService from '../../services/status_poster/status_poster.service.js' @@ -33,7 +32,7 @@ const mediaUpload = { } // Skip if image compression is disabled - if (!this.$store.getters.mergedConfig.imageCompression) { + if (!useSyncConfigStore().mergedConfig.imageCompression) { return file } @@ -78,7 +77,7 @@ const mediaUpload = { // Convert to WebP if supported and alwaysUseJpeg is false, otherwise JPEG const type = - !this.$store.getters.mergedConfig.alwaysUseJpeg && supportsWebP + !useSyncConfigStore().mergedConfig.alwaysUseJpeg && supportsWebP ? 'image/webp' : 'image/jpeg' const extension = type === 'image/webp' ? '.webp' : '.jpg' diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 4c211b5e9..d9580d8a2 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -1,6 +1,8 @@ import { defineAsyncComponent } from 'vue' import { mapGetters, mapState } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { highlightClass, @@ -97,7 +99,7 @@ const MentionLink = { return this.user && this.user.screen_name_ui }, highlight() { - return this.user && this.mergedConfig.highlight[this.user.screen_name] + return this.user && useSyncConfigStore().mergedConfig.highlight[this.user.screen_name] }, highlightType() { return this.highlight && '-' + this.highlight.type @@ -130,7 +132,7 @@ const MentionLink = { return this.userName !== this.userNameFull }, shouldShowFullUserName() { - const conf = this.mergedConfig.mentionLinkDisplay + const conf = useSyncConfigStore().mergedConfig.mentionLinkDisplay if (conf === 'short') { return false } else if (conf === 'full') { @@ -140,25 +142,16 @@ const MentionLink = { return this.isRemote } }, - shouldShowTooltip() { - return this.mergedConfig.mentionLinkShowTooltip - }, - shouldShowAvatar() { - return this.mergedConfig.mentionLinkShowAvatar - }, - shouldShowYous() { - return this.mergedConfig.mentionLinkShowYous - }, - shouldBoldenYou() { - return this.mergedConfig.mentionLinkBoldenYou - }, - shouldFadeDomain() { - return this.mergedConfig.mentionLinkFadeDomain - }, - ...mapGetters(['mergedConfig']), ...mapState({ currentUser: (state) => state.users.currentUser, }), + ...mapPiniaState(useSyncConfigStore, { + shouldShowTooltip: (state) => state.mergedConfig.mentionLinkShowTooltip, + shouldShowAvatar: (state) => state.mergedConfig.mentionLinkShowAvatar, + shouldShowYous: (state) => state.mergedConfig.mentionLinkShowYous, + shouldBoldenYou: (state) => state.mergedConfig.mentionLinkBoldenYou, + shouldFadeDomain: (state) => state.mergedConfig.mentionLinkFadeDomain, + }), }, } diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index 873bdc40d..33767762e 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -3,7 +3,8 @@ import { mapGetters } from 'vuex' import NavigationPins from 'src/components/navigation/navigation_pins.vue' import { useAnnouncementsStore } from 'src/stores/announcements' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useInstanceStore } from 'src/stores/instance.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import GestureService from '../../services/gesture_service/gesture_service' import { countExtraNotifications, @@ -64,24 +65,24 @@ const MobileNav = { return `${this.unseenCount ? this.unseenCount : ''}` }, hideSitename() { - return this.$store.state.instance.hideSitename + return useInstanceStore().hideSitename }, sitename() { - return this.$store.state.instance.name + return useInstanceStore().name }, isChat() { return this.$route.name === 'chat' }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), - ...mapState(useServerSideStorageStore, { + ...mapState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'), }), shouldConfirmLogout() { - return this.$store.getters.mergedConfig.modalOnLogout + return useSyncConfigStore().mergedConfig.modalOnLogout }, closingDrawerMarksAsSeen() { - return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen + return useSyncConfigStore().mergedConfig.closingDrawerMarksAsSeen }, ...mapGetters(['unreadChatCount']), }, diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js index dfa7b6fe2..115c2ab4c 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.js +++ b/src/components/mobile_post_status_button/mobile_post_status_button.js @@ -1,6 +1,7 @@ import { debounce } from 'lodash' import { usePostStatusStore } from 'src/stores/post_status' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faPen } from '@fortawesome/free-solid-svg-icons' @@ -45,10 +46,10 @@ const MobilePostStatusButton = { ) }, isPersistent() { - return !!this.$store.getters.mergedConfig.alwaysShowNewPostButton + return !!useSyncConfigStore().mergedConfig.alwaysShowNewPostButton }, autohideFloatingPostButton() { - return !!this.$store.getters.mergedConfig.autohideFloatingPostButton + return !!useSyncConfigStore().mergedConfig.autohideFloatingPostButton }, }, watch: { diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js index fdb8a74b6..f4c5e0470 100644 --- a/src/components/moderation_tools/moderation_tools.js +++ b/src/components/moderation_tools/moderation_tools.js @@ -1,3 +1,4 @@ +import { useInstanceStore } from 'src/stores/instance.js' import DialogModal from '../dialog_modal/dialog_modal.vue' import Popover from '../popover/popover.vue' @@ -54,7 +55,7 @@ const ModerationTools = { }, canUseTagPolicy() { return ( - this.$store.state.instance.tagPolicyAvailable && + useInstanceStore().tagPolicyAvailable && this.privileged('users_manage_tags') ) }, diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 36b6fe95d..97364fab7 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -10,7 +10,7 @@ import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue' import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useInstanceStore } from 'src/stores/instance.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -82,28 +82,28 @@ const NavPanel = { this.editMode = !this.editMode }, toggleCollapse() { - useServerSideStorageStore().setPreference({ + useSyncConfigStore().setPreference({ path: 'simple.collapseNav', value: !this.collapsed, }) - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, isPinned(item) { return this.pinnedItems.has(item) }, togglePin(item) { if (this.isPinned(item)) { - useServerSideStorageStore().removeCollectionPreference({ + useSyncConfigStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value: item, }) } else { - useServerSideStorageStore().addCollectionPreference({ + useSyncConfigStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value: item, }) } - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, }, computed: { @@ -111,7 +111,7 @@ const NavPanel = { unreadAnnouncementCount: 'unreadAnnouncementCount', supportsAnnouncements: (store) => store.supportsAnnouncements, }), - ...mapPiniaState(useServerSideStorageStore, { + ...mapPiniaState(useSyncConfigStore, { collapsed: (store) => store.prefsStorage.simple.collapseNav, pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), @@ -123,12 +123,12 @@ const NavPanel = { store.featureSet.pleromaChatMessagesAvailable, bookmarkFolders: (store) => store.featureSet.pleromaBookmarkFoldersAvailable, + privateMode: (state) => state.private, + federating: (state) => state.federating, }), ...mapState({ currentUser: (state) => state.users.currentUser, followRequestCount: (state) => state.api.followRequests.length, - privateMode: (state) => state.instance.private, - federating: (state) => state.instance.federating, }), timelinesItems() { return filterNavigation( diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js index 75d4dffdd..46f1695ad 100644 --- a/src/components/navigation/navigation_entry.js +++ b/src/components/navigation/navigation_entry.js @@ -4,7 +4,7 @@ import { mapState } from 'vuex' import { routeTo } from 'src/components/navigation/navigation.js' import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue' import { useAnnouncementsStore } from 'src/stores/announcements' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faThumbtack } from '@fortawesome/free-solid-svg-icons' @@ -22,17 +22,17 @@ const NavigationEntry = { }, togglePin(value) { if (this.isPinned(value)) { - useServerSideStorageStore().removeCollectionPreference({ + useSyncConfigStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value, }) } else { - useServerSideStorageStore().addCollectionPreference({ + useSyncConfigStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value, }) } - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, }, computed: { @@ -46,7 +46,7 @@ const NavigationEntry = { ...mapState({ currentUser: (state) => state.users.currentUser, }), - ...mapPiniaState(useServerSideStorageStore, { + ...mapPiniaState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js index c4979086f..99b5292eb 100644 --- a/src/components/navigation/navigation_pins.js +++ b/src/components/navigation/navigation_pins.js @@ -14,9 +14,9 @@ import { import StillImage from 'src/components/still-image/still-image.vue' import { useAnnouncementsStore } from 'src/stores/announcements' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' -import { useListsStore } from 'src/stores/lists' import { useInstanceStore } from 'src/stores/instance' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useListsStore } from 'src/stores/lists' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -68,15 +68,17 @@ const NavPanel = { ...mapPiniaState(useBookmarkFoldersStore, { bookmarks: getBookmarkFolderEntries, }), - ...mapPiniaState(useServerSideStorageStore, { + ...mapPiniaState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), ...mapPiniaState(useInstanceStore, { privateMode: (store) => store.featureSet.private, federating: (store) => store.featureSet.federating, - pleromaChatMessagesAvailable: (store) => store.featureSet.pleromaChatMessagesAvailable, - bubbleTimelinesSupported: (store) => store.featureSet.localBubbleInstances.length > 0, + pleromaChatMessagesAvailable: (store) => + store.featureSet.pleromaChatMessagesAvailable, + bubbleTimelinesSupported: (store) => + store.featureSet.localBubbleInstances.length > 0, }), ...mapState({ currentUser: (state) => state.users.currentUser, diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 275827c43..35644d070 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -2,6 +2,8 @@ import { mapState } from 'vuex' import RichContent from 'src/components/rich_content/rich_content.jsx' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { useInstanceStore } from 'src/stores/instance.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' import { highlightClass, @@ -107,7 +109,7 @@ const Notification = { return generateProfileLink( user.id, user.screen_name, - this.$store.state.instance.restrictedNicknames, + useInstanceStore().restrictedNicknames, ) }, getUser(notification) { @@ -178,7 +180,7 @@ const Notification = { return highlightClass(this.notification.from_profile) }, userStyle() { - const highlight = this.$store.getters.mergedConfig.highlight + const highlight = useSyncConfigStore().mergedConfig.highlight const user = this.notification.from_profile return highlightStyle(highlight[user.screen_name]) }, @@ -206,7 +208,7 @@ const Notification = { return isStatusNotification(this.notification.type) }, mergedConfig() { - return this.$store.getters.mergedConfig + return useSyncConfigStore().mergedConfig }, shouldConfirmApprove() { return this.mergedConfig.modalOnApproveFollow diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue index 7be8eb76b..d01e5e9ab 100644 --- a/src/components/notifications/notification_filters.vue +++ b/src/components/notifications/notification_filters.vue @@ -106,6 +106,7 @@ + +