diff --git a/build/emojis_plugin.js b/build/emojis_plugin.js index 9872f5331..7979086dd 100644 --- a/build/emojis_plugin.js +++ b/build/emojis_plugin.js @@ -25,11 +25,7 @@ const getAllAccessibleAnnotations = async (projectRoot) => { await access(importFile) return `'${lang}': () => import('${importModule}')` } catch (e) { - if (e.message.match(/ENOENT/)) { - console.warn(`Missing emoji annotations locale: ${destLang}`) - } else { - console.error('test', e.message) - } + console.error(e) return } }), diff --git a/public/static/config.json b/public/static/config.json index 8a78284a3..6df21bb9e 100644 --- a/public/static/config.json +++ b/public/static/config.json @@ -25,7 +25,7 @@ "sidebarRight": false, "subjectLineBehavior": "email", "theme": null, - "style": null, - "palette": null, + "style": "BreezyDX", + "palette": "sigsegv2", "webPushNotifications": false } diff --git a/src/App.js b/src/App.js index c7862a8fb..174d91da4 100644 --- a/src/App.js +++ b/src/App.js @@ -135,11 +135,13 @@ export default { userBackground() { return this.currentUser.background_image }, - instanceBackground() { - return this.mergedConfig.hideInstanceWallpaper ? null : this.background - }, background() { - return this.userBackground || this.instanceBackground + return ( + this.userBackground || + (this.mergedConfig.hideInstanceWallpaper + ? null + : this.instanceBackground) + ) }, bgStyle() { if (this.background) { @@ -209,7 +211,7 @@ export default { 'editingAvailable', ]), ...mapState(useInstanceStore, { - background: (store) => store.instanceIdentity.background, + instanceBackground: (store) => store.instanceIdentity.background, showFeaturesPanel: (store) => store.instanceIdentity.showFeaturesPanel, instanceSpecificPanelPresent: (store) => store.instanceIdentity.showInstanceSpecificPanel && diff --git a/src/boot/after_store.js b/src/boot/after_store.js index b87ff8924..97f9b8a42 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -33,7 +33,6 @@ 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 { @@ -548,7 +547,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { return Promise.reject(e) } - applyStyleConfig(useSyncConfigStore().mergedConfig, i18n.global) + applyStyleConfig(store.state.config, 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 diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 23de501de..4917a11fb 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -2,7 +2,6 @@ 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' @@ -11,6 +10,8 @@ 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' @@ -136,7 +137,7 @@ const EmojiInput = { 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) @@ -165,7 +166,7 @@ const EmojiInput = { }, languages() { return ensureFinalFallback( - this.$store.getters.mergedConfig.interfaceLanguage, + useSyncConfigStore().mergedConfig.interfaceLanguage, ) }, maybeLocalizedEmojiNamesAndKeywords() { @@ -333,7 +334,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/notifications/notifications.js b/src/components/notifications/notifications.js index 544901ebf..cebabc812 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -81,21 +81,15 @@ const Notifications = { filteredNotifications() { if (this.unseenAtTop) { return [ - ...filteredNotificationsFromStore( - this.$store, - useSyncConfigStore().mergedConfig.notificationVisibility, - ).filter((n) => this.shouldShowUnseen(n)), - ...filteredNotificationsFromStore( - this.$store, - useSyncConfigStore().mergedConfig.notificationVisibility, - ).filter((n) => !this.shouldShowUnseen(n)), + ...filteredNotificationsFromStore(this.$store).filter((n) => + this.shouldShowUnseen(n), + ), + ...filteredNotificationsFromStore(this.$store).filter( + (n) => !this.shouldShowUnseen(n), + ), ] } else { - return filteredNotificationsFromStore( - this.$store, - useSyncConfigStore().mergedConfig.notificationVisibility, - this.filterMode, - ) + return filteredNotificationsFromStore(this.$store, this.filterMode) } }, unseenCountBadgeText() { diff --git a/src/components/still-image/still-image-emoji-popover.js b/src/components/still-image/still-image-emoji-popover.js new file mode 100644 index 000000000..0d382d357 --- /dev/null +++ b/src/components/still-image/still-image-emoji-popover.js @@ -0,0 +1,127 @@ +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 + }) + }, + }, +} diff --git a/src/components/timeago/timeago.js b/src/components/timeago/timeago.js index efb92d4cd..a9e874862 100644 --- a/src/components/timeago/timeago.js +++ b/src/components/timeago/timeago.js @@ -1,3 +1,5 @@ +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' @@ -13,17 +15,17 @@ export default { }, computed: { shouldUseAbsoluteTimeFormat() { - if (!this.$store.getters.mergedConfig.useAbsoluteTimeFormat) { + if (!useSyncConfigStore().mergedConfig.useAbsoluteTimeFormat) { return false } return ( DateUtils.durationStrToMs( - this.$store.getters.mergedConfig.absoluteTimeFormatMinAge, + useSyncConfigStore().mergedConfig.absoluteTimeFormatMinAge, ) <= this.relativeTimeMs ) }, time12hFormat() { - return this.$store.getters.mergedConfig.absoluteTimeFormat12h === '12h' + return useSyncConfigStore().mergedConfig.absoluteTimeFormat12h === '12h' }, browserLocale() { return localeService.internalToBrowserLocale(this.$i18n.locale) diff --git a/src/lib/language.js b/src/lib/language.js index 68e3a0989..f8904dd07 100644 --- a/src/lib/language.js +++ b/src/lib/language.js @@ -1,13 +1,4 @@ -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') { @@ -16,12 +7,6 @@ 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), - ) } } }) diff --git a/src/lib/push_notifications_plugin.js b/src/lib/push_notifications_plugin.js index 7d44625c2..0dfe9588b 100644 --- a/src/lib/push_notifications_plugin.js +++ b/src/lib/push_notifications_plugin.js @@ -59,8 +59,10 @@ 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') } }) @@ -85,8 +87,10 @@ 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') } } diff --git a/src/modules/config_declaration.js b/src/modules/config_declaration.js index bf6a5cd7b..5c0d7ac0e 100644 --- a/src/modules/config_declaration.js +++ b/src/modules/config_declaration.js @@ -1,5 +1,3 @@ -import { useSyncConfigStore } from 'src/stores/sync_config.js' - export const CONFIG_MIGRATION = 1 import { v4 as uuidv4 } from 'uuid' @@ -24,7 +22,7 @@ export const declarations = [ description: 'Mute filters, wordfilter/regexp/etc', valueType: 'complex', migration(serverside, rootState) { - useSyncConfigStore().mergedConfig.muteWords.forEach((word, order) => { + rootState.config.muteWords.forEach((word, order) => { const uniqueId = uuidv4() serverside.setPreference({ diff --git a/src/modules/users.js b/src/modules/users.js index a22c157ed..ea2a0ccb4 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -553,9 +553,9 @@ const users = { registerPushNotifications(store) { const token = store.state.currentUser.credentials const vapidPublicKey = useInstanceStore().vapidPublicKey - const isEnabled = useSyncConfigStore().mergedConfig.webPushNotifications + const isEnabled = store.rootState.config.webPushNotifications const notificationVisibility = - useSyncConfigStore().mergedConfig.notificationVisibility + store.rootState.config.notificationVisibility registerPushNotifications( isEnabled, diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index e5baa9a20..14785a685 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -16,7 +16,12 @@ let cachedBadgeUrl = null export const notificationsFromStore = (store) => store.state.notifications.data -const visibleTypes = (notificationVisibility) => { +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 + return [ notificationVisibility.likes && 'like', notificationVisibility.mentions && 'mention', @@ -71,15 +76,11 @@ const isMutedNotification = (notification) => { return muteFilterHits(notification.status).length > 0 } -export const maybeShowNotification = ( - store, - notificationVisibility, - notification, -) => { +export const maybeShowNotification = (store, notification) => { const rootState = store.rootState || store.state if (notification.seen) return - if (!visibleTypes(notificationVisibility).includes(notification.type)) return + if (!visibleTypes(store).includes(notification.type)) return if (notification.type === 'mention' && isMutedNotification(notification)) return @@ -90,18 +91,14 @@ export const maybeShowNotification = ( showDesktopNotification(rootState, notificationObject) } -export const filteredNotificationsFromStore = ( - store, - notificationVisibility, - types, -) => { +export const filteredNotificationsFromStore = (store, 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(notificationVisibility)).includes(notification.type), + (types || visibleTypes(store)).includes(notification.type), ) } diff --git a/src/stores/i18n.js b/src/stores/i18n.js index fee09f4bd..a18b1d4d2 100644 --- a/src/stores/i18n.js +++ b/src/stores/i18n.js @@ -1,6 +1,14 @@ 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, @@ -11,5 +19,16 @@ 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), + ) + }, }, }) diff --git a/src/stores/interface.js b/src/stores/interface.js index dfe788262..8d05ce44b 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -8,7 +8,6 @@ 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, @@ -403,13 +402,13 @@ export const useInterfaceStore = defineStore('interface', { styleCustomData: userStyleCustomData, palette: userPaletteName, paletteCustomData: userPaletteCustomData, - } = useSyncConfigStore().mergedConfig + } = window.vuex.state.config let { theme: userThemeV2Name, customTheme: userThemeV2Snapshot, customThemeSource: userThemeV2Source, - } = useSyncConfigStore().mergedConfig + } = window.vuex.state.config let majorVersionUsed @@ -574,7 +573,7 @@ export const useInterfaceStore = defineStore('interface', { }, async applyTheme({ recompile = false } = {}) { const { forceThemeRecompilation, themeDebug, theme3hacks } = - useSyncConfigStore().mergedConfig + window.vuex.state.config this.themeChangeInProgress = true // If we're not forced to recompile try using // cache (tryLoadCache return true if load successful) diff --git a/src/sw.js b/src/sw.js index e3225c91d..3e22cca68 100644 --- a/src/sw.js +++ b/src/sw.js @@ -4,10 +4,11 @@ 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 @@ -38,7 +39,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 diff --git a/test/unit/specs/components/draft.spec.js b/test/unit/specs/components/draft.spec.js index 386b2a761..7bc053f52 100644 --- a/test/unit/specs/components/draft.spec.js +++ b/test/unit/specs/components/draft.spec.js @@ -1,13 +1,10 @@ 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) @@ -39,19 +36,17 @@ afterEach(() => { }) describe('Draft saving', () => { - beforeEach(() => { - setActivePinia(createTestingPinia()) - }) + createTestingPinia() autoSaveOrNot( it, 'should save when the button is clicked', async (autoSave) => { const wrapper = mount(PostStatusForm, mountOpts()) - const store = useSyncConfigStore() - store.mergedConfig = { - autoSaveDraft: autoSave, - } + await wrapper.vm.$store.dispatch('setOption', { + name: 'autoSaveDraft', + value: autoSave, + }) expect(wrapper.vm.$store.getters.draftCount).to.equal(0) const textarea = wrapper.get('textarea') @@ -68,10 +63,10 @@ describe('Draft saving', () => { it('should auto-save if it is enabled', async function () { vi.useFakeTimers() const wrapper = mount(PostStatusForm, mountOpts()) - const store = useSyncConfigStore() - store.mergedConfig = { - autoSaveDraft: true, - } + await wrapper.vm.$store.dispatch('setOption', { + name: 'autoSaveDraft', + value: true, + }) expect(wrapper.vm.$store.getters.draftCount).to.equal(0) const textarea = wrapper.get('textarea') await textarea.setValue('mew mew') @@ -91,10 +86,10 @@ describe('Draft saving', () => { }, }), ) - const store = useSyncConfigStore() - store.mergedConfig = { - autoSaveDraft: true, - } + await wrapper.vm.$store.dispatch('setOption', { + name: 'autoSaveDraft', + value: true, + }) expect(wrapper.vm.$store.getters.draftCount).to.equal(0) const textarea = wrapper.get('textarea') await textarea.setValue('mew mew') @@ -112,11 +107,14 @@ describe('Draft saving', () => { }, }), ) - const store = useSyncConfigStore() - store.mergedConfig = { - autoSaveDraft: false, - unsavedPostAction: 'save', - } + await wrapper.vm.$store.dispatch('setOption', { + name: 'autoSaveDraft', + value: false, + }) + await wrapper.vm.$store.dispatch('setOption', { + name: 'unsavedPostAction', + value: 'save', + }) expect(wrapper.vm.$store.getters.draftCount).to.equal(0) const textarea = wrapper.get('textarea') await textarea.setValue('mew mew') @@ -134,11 +132,14 @@ describe('Draft saving', () => { }, }), ) - const store = useSyncConfigStore() - store.mergedConfig = { - autoSaveDraft: false, - unsavedPostAction: 'discard', - } + await wrapper.vm.$store.dispatch('setOption', { + name: 'autoSaveDraft', + value: false, + }) + await wrapper.vm.$store.dispatch('setOption', { + name: 'unsavedPostAction', + value: 'discard', + }) expect(wrapper.vm.$store.getters.draftCount).to.equal(0) const textarea = wrapper.get('textarea') await textarea.setValue('mew mew') @@ -156,11 +157,14 @@ describe('Draft saving', () => { }, }), ) - const store = useSyncConfigStore(createTestingPinia()) - store.mergedConfig = { - autoSaveDraft: false, - unsavedPostAction: 'confirm', - } + await wrapper.vm.$store.dispatch('setOption', { + name: 'autoSaveDraft', + value: false, + }) + await wrapper.vm.$store.dispatch('setOption', { + name: 'unsavedPostAction', + value: 'confirm', + }) expect(wrapper.vm.$store.getters.draftCount).to.equal(0) const textarea = wrapper.get('textarea') await textarea.setValue('mew mew') diff --git a/test/unit/specs/services/notification_utils/notification_utils.spec.js b/test/unit/specs/services/notification_utils/notification_utils.spec.js index 0db318e3c..2bfd3c4a1 100644 --- a/test/unit/specs/services/notification_utils/notification_utils.spec.js +++ b/test/unit/specs/services/notification_utils/notification_utils.spec.js @@ -40,6 +40,9 @@ describe('NotificationUtils', () => { ], }, }, + getters: { + mergedConfig: {}, + }, } const expected = [ {