Compare commits

..

27 commits

Author SHA1 Message Date
Henry Jameson
63bffe73db Merge remote-tracking branch 'origin/develop' into setttingssync 2026-02-13 15:37:19 +02:00
HJ
2ce11e56d4 Merge pull request 'misc-fixes' (#3475) from misc-fixes into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3475
2026-02-13 13:35:48 +00:00
Henry Jameson
dbdf81d8b3 undo sync-config-related changes 2026-02-13 15:34:37 +02:00
Henry Jameson
496099bb00 made it work 2026-02-13 15:21:59 +02:00
Henry Jameson
6967151275 move language switch logic entirely into plugin 2026-02-13 15:21:59 +02:00
Henry Jameson
ea020cefdb typo 2026-02-13 15:21:59 +02:00
Henry Jameson
6f3f75d9b4 i18n
This reverts commit 829018b656c52e8f8dc8bd5a9f74e83f46b71567.
2026-02-13 15:21:59 +02:00
Henry Jameson
2c4cb8a67a rollback language.js
This reverts commit 37d0a6d3f1b277f4470618a249dca77f14c8c11f.
2026-02-13 15:21:59 +02:00
Henry Jameson
0e84cffa41 wip migration change 2026-02-13 15:21:59 +02:00
Henry Jameson
d2a870ac96 better handling of SW subscription 2026-02-13 15:21:59 +02:00
Henry Jameson
76d3ec1b39 Revert "notification_utils.js"
This reverts commit 67059addfe64d3cf531a4797a3b9d38174f622e4.
2026-02-13 15:21:59 +02:00
Henry Jameson
85976a61b8 notification_utils.js 2026-02-13 15:21:59 +02:00
Henry Jameson
682ad334c1 users.js 2026-02-13 15:21:59 +02:00
Henry Jameson
2c673f439f interface.js 2026-02-13 15:21:59 +02:00
Henry Jameson
71172ec93a after_store.js 2026-02-13 15:21:54 +02:00
Henry Jameson
d3d4b899d2 less spooky errors 2026-02-13 15:21:20 +02:00
Henry Jameson
b08df84282 App.js 2026-02-13 15:21:20 +02:00
Henry Jameson
dbc9bd9c46 components 2026-02-13 15:21:20 +02:00
Henry Jameson
c9dede920e main.js 2026-02-13 15:21:20 +02:00
Henry Jameson
8fabbe9525 semi-related changes 2026-02-13 15:20:48 +02:00
Henry Jameson
0688cdab86 unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
5dc6ca6d58 unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
dc96e5ac53 another unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
fc66830138 another unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
cd66dabf94 unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
69656e0181 minor change for paths in SW 2026-02-13 14:07:41 +02:00
Henry Jameson
29e71c8a26 sss -> sc 2026-02-13 14:06:36 +02:00
18 changed files with 108 additions and 238 deletions

View file

@ -25,7 +25,11 @@ const getAllAccessibleAnnotations = async (projectRoot) => {
await access(importFile)
return `'${lang}': () => import('${importModule}')`
} catch (e) {
console.error(e)
if (e.message.match(/ENOENT/)) {
console.warn(`Missing emoji annotations locale: ${destLang}`)
} else {
console.error('test', e.message)
}
return
}
}),

View file

@ -25,7 +25,7 @@
"sidebarRight": false,
"subjectLineBehavior": "email",
"theme": null,
"style": "BreezyDX",
"palette": "sigsegv2",
"style": null,
"palette": null,
"webPushNotifications": false
}

View file

@ -135,13 +135,11 @@ export default {
userBackground() {
return this.currentUser.background_image
},
instanceBackground() {
return this.mergedConfig.hideInstanceWallpaper ? null : this.background
},
background() {
return (
this.userBackground ||
(this.mergedConfig.hideInstanceWallpaper
? null
: this.instanceBackground)
)
return this.userBackground || this.instanceBackground
},
bgStyle() {
if (this.background) {
@ -211,7 +209,7 @@ export default {
'editingAvailable',
]),
...mapState(useInstanceStore, {
instanceBackground: (store) => store.instanceIdentity.background,
background: (store) => store.instanceIdentity.background,
showFeaturesPanel: (store) => store.instanceIdentity.showFeaturesPanel,
instanceSpecificPanelPresent: (store) =>
store.instanceIdentity.showInstanceSpecificPanel &&

View file

@ -33,6 +33,7 @@ import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useOAuthStore } from 'src/stores/oauth'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import {
@ -547,7 +548,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
return Promise.reject(e)
}
applyStyleConfig(store.state.config, i18n.global)
applyStyleConfig(useSyncConfigStore().mergedConfig, i18n.global)
// Now we can try getting the server settings and logging in
// Most of these are preloaded into the index.html so blocking is minimized

View file

@ -2,6 +2,7 @@ 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 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'
@ -10,8 +11,6 @@ import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_i
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { ensureFinalFallback } from 'src/i18n/languages.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faSmileBeam } from '@fortawesome/free-regular-svg-icons'
@ -137,7 +136,7 @@ const EmojiInput = {
return useSyncConfigStore().mergedConfig.padEmoji
},
defaultCandidateIndex() {
return useSyncConfigStore().mergedConfig.autocompleteSelect ? 0 : -1
return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1
},
preText() {
return this.modelValue.slice(0, this.caret)
@ -166,7 +165,7 @@ const EmojiInput = {
},
languages() {
return ensureFinalFallback(
useSyncConfigStore().mergedConfig.interfaceLanguage,
this.$store.getters.mergedConfig.interfaceLanguage,
)
},
maybeLocalizedEmojiNamesAndKeywords() {
@ -334,6 +333,7 @@ const EmojiInput = {
if (!this.pickerShown) {
this.scrollIntoView()
this.$refs.picker.showPicker()
this.$refs.picker.startEmojiLoad()
} else {
this.$refs.picker.hidePicker()
}

View file

@ -81,15 +81,21 @@ const Notifications = {
filteredNotifications() {
if (this.unseenAtTop) {
return [
...filteredNotificationsFromStore(this.$store).filter((n) =>
this.shouldShowUnseen(n),
),
...filteredNotificationsFromStore(this.$store).filter(
(n) => !this.shouldShowUnseen(n),
),
...filteredNotificationsFromStore(
this.$store,
useSyncConfigStore().mergedConfig.notificationVisibility,
).filter((n) => this.shouldShowUnseen(n)),
...filteredNotificationsFromStore(
this.$store,
useSyncConfigStore().mergedConfig.notificationVisibility,
).filter((n) => !this.shouldShowUnseen(n)),
]
} else {
return filteredNotificationsFromStore(this.$store, this.filterMode)
return filteredNotificationsFromStore(
this.$store,
useSyncConfigStore().mergedConfig.notificationVisibility,
this.filterMode,
)
}
},
unseenCountBadgeText() {

View file

@ -1,127 +0,0 @@
import Popover from 'components/popover/popover.vue'
import SelectComponent from 'components/select/select.vue'
import { assign } from 'lodash'
import StillImage from './still-image.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface'
export default {
components: { StillImage, Popover, SelectComponent },
props: {
shortcode: {
type: String,
required: true,
},
isLocal: {
type: Boolean,
required: true,
},
},
data() {
return {
knownLocalPacks: {},
packName: '',
}
},
computed: {
isUserAdmin() {
return this.$store.state.users.currentUser.rights.admin
},
},
methods: {
displayError(msg) {
useInterfaceStore().pushGlobalNotice({
messageKey: 'admin_dash.emoji.error',
messageArgs: [msg],
level: 'error',
})
},
copyToLocalPack() {
this.$store.state.api.backendInteractor
.addNewEmojiFile({
packName: this.packName,
file: this.$attrs.src,
shortcode: this.shortcode,
filename: '',
})
.then((resp) => resp.json())
.then((resp) => {
if (resp.error !== undefined) {
this.displayError(resp.error)
return
}
useInterfaceStore().pushGlobalNotice({
messageKey: 'admin_dash.emoji.copied_successfully',
messageArgs: [this.shortcode, this.packName],
level: 'success',
})
this.$refs.emojiPopover.hidePopover()
this.packName = ''
})
},
// Copied from emoji_tab.js
loadPacksPaginated(listFunction) {
const pageSize = 25
const allPacks = {}
return listFunction({
instance: useInstanceStore().server,
page: 1,
pageSize: 0,
})
.then((data) => data.json())
.then((data) => {
if (data.error !== undefined) {
return Promise.reject(data.error)
}
let resultingPromise = Promise.resolve({})
for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
resultingPromise = resultingPromise
.then(() =>
listFunction({
instance: useInstanceStore().server,
page: i,
pageSize,
}),
)
.then((data) => data.json())
.then((pageData) => {
if (pageData.error !== undefined) {
return Promise.reject(pageData.error)
}
assign(allPacks, pageData.packs)
})
}
return resultingPromise
})
.then(() => allPacks)
.catch((data) => {
this.displayError(data)
})
},
fetchEmojiPacksIfAdmin() {
if (!this.isUserAdmin) return
this.loadPacksPaginated(
this.$store.state.api.backendInteractor.listEmojiPacks,
).then((allPacks) => {
// Sort by key
const sorted = Object.keys(allPacks)
.sort()
.reduce((acc, key) => {
if (key.length === 0) return acc
acc[key] = allPacks[key]
return acc
}, {})
this.knownLocalPacks = sorted
})
},
},
}

View file

@ -1,5 +1,3 @@
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import * as DateUtils from 'src/services/date_utils/date_utils.js'
import localeService from 'src/services/locale/locale.service.js'
@ -15,17 +13,17 @@ export default {
},
computed: {
shouldUseAbsoluteTimeFormat() {
if (!useSyncConfigStore().mergedConfig.useAbsoluteTimeFormat) {
if (!this.$store.getters.mergedConfig.useAbsoluteTimeFormat) {
return false
}
return (
DateUtils.durationStrToMs(
useSyncConfigStore().mergedConfig.absoluteTimeFormatMinAge,
this.$store.getters.mergedConfig.absoluteTimeFormatMinAge,
) <= this.relativeTimeMs
)
},
time12hFormat() {
return useSyncConfigStore().mergedConfig.absoluteTimeFormat12h === '12h'
return this.$store.getters.mergedConfig.absoluteTimeFormat12h === '12h'
},
browserLocale() {
return localeService.internalToBrowserLocale(this.$i18n.locale)

View file

@ -1,4 +1,13 @@
import Cookies from 'js-cookie'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useI18nStore } from 'src/stores/i18n.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import messages from 'src/i18n/messages'
import localeService from 'src/services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
export const piniaLanguagePlugin = ({ store, options }) => {
if (store.$id === 'sync_config') {
@ -7,6 +16,12 @@ export const piniaLanguagePlugin = ({ store, options }) => {
const { path, value } = args[0]
if (path === 'simple.interfaceLanguage') {
useI18nStore().setLanguage(value)
messages.setLanguage(this.i18n, value)
useEmojiStore().loadUnicodeEmojiData(value)
Cookies.set(
BACKEND_LANGUAGE_COOKIE_NAME,
localeService.internalToBackendLocaleMulti(value),
)
}
}
})

View file

@ -59,10 +59,8 @@ export const piniaPushNotificationsPlugin = ({ store }) => {
}
if (permissionGranted && enabled && user) {
console.log('piniaReg')
return window.vuex.dispatch('registerPushNotifications')
} else {
console.log('piniaUnreg')
return window.vuex.dispatch('unregisterPushNotifications')
}
})
@ -87,10 +85,8 @@ export const vuexPushNotificationsPlugin = (store) => {
) {
console.log(!!user, permissionGranted, enabled)
if (user && permissionGranted && enabled) {
console.log('vuexReg')
return store.dispatch('registerPushNotifications')
} else {
console.log('vuexUnreg')
return store.dispatch('unregisterPushNotifications')
}
}

View file

@ -1,3 +1,5 @@
import { useSyncConfigStore } from 'src/stores/sync_config.js'
export const CONFIG_MIGRATION = 1
import { v4 as uuidv4 } from 'uuid'
@ -22,7 +24,7 @@ export const declarations = [
description: 'Mute filters, wordfilter/regexp/etc',
valueType: 'complex',
migration(serverside, rootState) {
rootState.config.muteWords.forEach((word, order) => {
useSyncConfigStore().mergedConfig.muteWords.forEach((word, order) => {
const uniqueId = uuidv4()
serverside.setPreference({

View file

@ -553,9 +553,9 @@ const users = {
registerPushNotifications(store) {
const token = store.state.currentUser.credentials
const vapidPublicKey = useInstanceStore().vapidPublicKey
const isEnabled = store.rootState.config.webPushNotifications
const isEnabled = useSyncConfigStore().mergedConfig.webPushNotifications
const notificationVisibility =
store.rootState.config.notificationVisibility
useSyncConfigStore().mergedConfig.notificationVisibility
registerPushNotifications(
isEnabled,

View file

@ -16,12 +16,7 @@ let cachedBadgeUrl = null
export const notificationsFromStore = (store) => store.state.notifications.data
export const visibleTypes = (store) => {
// When called from within a module we need rootGetters to access wider scope
// however when called from a component (i.e. this.$store) we already have wider scope
const rootGetters = store.rootGetters || store.getters
const { notificationVisibility } = rootGetters.mergedConfig
const visibleTypes = (notificationVisibility) => {
return [
notificationVisibility.likes && 'like',
notificationVisibility.mentions && 'mention',
@ -76,11 +71,15 @@ const isMutedNotification = (notification) => {
return muteFilterHits(notification.status).length > 0
}
export const maybeShowNotification = (store, notification) => {
export const maybeShowNotification = (
store,
notificationVisibility,
notification,
) => {
const rootState = store.rootState || store.state
if (notification.seen) return
if (!visibleTypes(store).includes(notification.type)) return
if (!visibleTypes(notificationVisibility).includes(notification.type)) return
if (notification.type === 'mention' && isMutedNotification(notification))
return
@ -91,14 +90,18 @@ export const maybeShowNotification = (store, notification) => {
showDesktopNotification(rootState, notificationObject)
}
export const filteredNotificationsFromStore = (store, types) => {
export const filteredNotificationsFromStore = (
store,
notificationVisibility,
types,
) => {
// map is just to clone the array since sort mutates it and it causes some issues
const sortedNotifications = notificationsFromStore(store)
.map((_) => _)
.sort(sortById)
// TODO implement sorting elsewhere and make it optional
return sortedNotifications.filter((notification) =>
(types || visibleTypes(store)).includes(notification.type),
(types || visibleTypes(notificationVisibility)).includes(notification.type),
)
}

View file

@ -1,14 +1,6 @@
import Cookies from 'js-cookie'
import { defineStore } from 'pinia'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import messages from 'src/i18n/messages'
import localeService from 'src/services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
export const useI18nStore = defineStore('i18n', {
state: () => ({
i18n: null,
@ -19,16 +11,5 @@ export const useI18nStore = defineStore('i18n', {
i18n: newI18n.global,
})
},
setLanguage(originalValue) {
const value =
originalValue || useSyncConfigStore().mergedConfig.interfaceLanguage
messages.setLanguage(this.i18n, value)
useEmojiStore().loadUnicodeEmojiData(value)
Cookies.set(
BACKEND_LANGUAGE_COOKIE_NAME,
localeService.internalToBackendLocaleMulti(value),
)
},
},
})

View file

@ -8,6 +8,7 @@ import {
import { deserialize } from '../services/theme_data/iss_deserializer.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import {
CURRENT_VERSION,
@ -402,13 +403,13 @@ export const useInterfaceStore = defineStore('interface', {
styleCustomData: userStyleCustomData,
palette: userPaletteName,
paletteCustomData: userPaletteCustomData,
} = window.vuex.state.config
} = useSyncConfigStore().mergedConfig
let {
theme: userThemeV2Name,
customTheme: userThemeV2Snapshot,
customThemeSource: userThemeV2Source,
} = window.vuex.state.config
} = useSyncConfigStore().mergedConfig
let majorVersionUsed
@ -573,7 +574,7 @@ export const useInterfaceStore = defineStore('interface', {
},
async applyTheme({ recompile = false } = {}) {
const { forceThemeRecompilation, themeDebug, theme3hacks } =
window.vuex.state.config
useSyncConfigStore().mergedConfig
this.themeChangeInProgress = true
// If we're not forced to recompile try using
// cache (tryLoadCache return true if load successful)

View file

@ -4,11 +4,10 @@ import 'virtual:pleroma-fe/service_worker_env'
import { createI18n } from 'vue-i18n'
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
import { cacheKey, emojiCacheKey, shouldCache } from './services/sw/sw.js'
import { storage } from 'src/lib/storage.js'
import { parseNotification } from 'src/services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from 'src/services/notification_utils/notification_utils.js'
import { cacheKey, emojiCacheKey, shouldCache } from 'src/services/sw/sw.js'
// Collects all messages for service workers
// Needed because service workers cannot use dynamic imports
@ -39,7 +38,7 @@ const setSettings = async () => {
const locale = vuexState.config.interfaceLanguage || 'en'
i18n.locale = locale
const notificationsNativeArray = Object.entries(
vuexState.config.notificationNative || {},
vuexState.config.notificationNative,
)
state.webPushAlwaysShowNotifications =
vuexState.config.webPushAlwaysShowNotifications

View file

@ -1,10 +1,13 @@
import { createTestingPinia } from '@pinia/testing'
import { flushPromises, mount } from '@vue/test-utils'
import { setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
import { $t, mountOpts, waitForEvent } from '../../../fixtures/setup_test'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
const autoSaveOrNot = (caseFn, caseTitle, runFn) => {
caseFn(`${caseTitle} with auto-save`, function () {
return runFn.bind(this)(true)
@ -36,17 +39,19 @@ afterEach(() => {
})
describe('Draft saving', () => {
createTestingPinia()
beforeEach(() => {
setActivePinia(createTestingPinia())
})
autoSaveOrNot(
it,
'should save when the button is clicked',
async (autoSave) => {
const wrapper = mount(PostStatusForm, mountOpts())
await wrapper.vm.$store.dispatch('setOption', {
name: 'autoSaveDraft',
value: autoSave,
})
const store = useSyncConfigStore()
store.mergedConfig = {
autoSaveDraft: autoSave,
}
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
const textarea = wrapper.get('textarea')
@ -63,10 +68,10 @@ describe('Draft saving', () => {
it('should auto-save if it is enabled', async function () {
vi.useFakeTimers()
const wrapper = mount(PostStatusForm, mountOpts())
await wrapper.vm.$store.dispatch('setOption', {
name: 'autoSaveDraft',
value: true,
})
const store = useSyncConfigStore()
store.mergedConfig = {
autoSaveDraft: true,
}
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
const textarea = wrapper.get('textarea')
await textarea.setValue('mew mew')
@ -86,10 +91,10 @@ describe('Draft saving', () => {
},
}),
)
await wrapper.vm.$store.dispatch('setOption', {
name: 'autoSaveDraft',
value: true,
})
const store = useSyncConfigStore()
store.mergedConfig = {
autoSaveDraft: true,
}
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
const textarea = wrapper.get('textarea')
await textarea.setValue('mew mew')
@ -107,14 +112,11 @@ describe('Draft saving', () => {
},
}),
)
await wrapper.vm.$store.dispatch('setOption', {
name: 'autoSaveDraft',
value: false,
})
await wrapper.vm.$store.dispatch('setOption', {
name: 'unsavedPostAction',
value: 'save',
})
const store = useSyncConfigStore()
store.mergedConfig = {
autoSaveDraft: false,
unsavedPostAction: 'save',
}
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
const textarea = wrapper.get('textarea')
await textarea.setValue('mew mew')
@ -132,14 +134,11 @@ describe('Draft saving', () => {
},
}),
)
await wrapper.vm.$store.dispatch('setOption', {
name: 'autoSaveDraft',
value: false,
})
await wrapper.vm.$store.dispatch('setOption', {
name: 'unsavedPostAction',
value: 'discard',
})
const store = useSyncConfigStore()
store.mergedConfig = {
autoSaveDraft: false,
unsavedPostAction: 'discard',
}
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
const textarea = wrapper.get('textarea')
await textarea.setValue('mew mew')
@ -157,14 +156,11 @@ describe('Draft saving', () => {
},
}),
)
await wrapper.vm.$store.dispatch('setOption', {
name: 'autoSaveDraft',
value: false,
})
await wrapper.vm.$store.dispatch('setOption', {
name: 'unsavedPostAction',
value: 'confirm',
})
const store = useSyncConfigStore(createTestingPinia())
store.mergedConfig = {
autoSaveDraft: false,
unsavedPostAction: 'confirm',
}
expect(wrapper.vm.$store.getters.draftCount).to.equal(0)
const textarea = wrapper.get('textarea')
await textarea.setValue('mew mew')

View file

@ -40,9 +40,6 @@ describe('NotificationUtils', () => {
],
},
},
getters: {
mergedConfig: {},
},
}
const expected = [
{