diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 1a2be5bd7..0b8f8b02a 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -22,7 +22,8 @@ import { import { useAnnouncementsStore } from 'src/stores/announcements' import { useAuthFlowStore } from 'src/stores/auth_flow' import { useI18nStore } from 'src/stores/i18n' -import { useInterfaceStore } from 'src/stores/interface' +import { useInterfaceStore } from 'src/stores/interface.js' +import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth' import App from '../App.vue' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' @@ -78,29 +79,29 @@ const getInstanceConfig = async ({ store }) => { const textlimit = data.max_toot_chars const vapidPublicKey = data.pleroma.vapid_public_key - store.dispatch('setInstanceOption', { - name: 'pleromaExtensionsAvailable', + useInstanceStore().set({ + name: 'featureSet.pleromaExtensionsAvailable', value: data.pleroma, }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'textlimit', value: textlimit, }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'accountApprovalRequired', value: data.approval_required, }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required, }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0, }) if (vapidPublicKey) { - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'vapidPublicKey', value: vapidPublicKey, }) @@ -161,14 +162,14 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { config = Object.assign({}, staticConfig, apiConfig) } - const copyInstanceOption = (name) => { + const copyInstanceOption = (path) => { if (typeof config[name] !== 'undefined') { - store.dispatch('setInstanceOption', { name, value: config[name] }) + useInstanceStore().set({ path, value: config[name] }) } } - Object.keys(staticOrApiConfigDefault).forEach(copyInstanceOption) - Object.keys(instanceDefaultConfig).forEach(copyInstanceOption) + Object.keys(staticOrApiConfigDefault).map(k => `instanceIdentity.${k}`).forEach(copyInstanceOption) + Object.keys(instanceDefaultConfig).map(k => `prefsStorage.${k}`).forEach(copyInstanceOption) useAuthFlowStore().setInitialStrategy(config.loginMethod) } @@ -178,7 +179,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({ name: 'tos', value: html }) } else { throw res } @@ -192,7 +193,7 @@ const getInstancePanel = async ({ store }) => { const res = await preloadFetch('/instance/panel.html') if (res.ok) { const html = await res.text() - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'instanceSpecificPanelContent', value: html, }) @@ -227,7 +228,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({ name: 'stickers', value: stickers }) } else { throw res } @@ -248,7 +249,7 @@ const getAppSecret = async ({ store }) => { const resolveStaffAccounts = ({ store, accounts }) => { const nicknames = accounts.map((uri) => uri.split('/').pop()) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'staffAccounts', value: nicknames, }) @@ -262,159 +263,163 @@ 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', - 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: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [], }) + useInstanceStore().set({ + path: 'featureSet.localBubble', + value: (metadata.localBubbleInstances ?? []).length > 0, + }) + useInstanceStore().set({ + path: 'limits.pollLimits', + value: metadata.pollLimits, + }) const uploadLimits = metadata.uploadLimits - store.dispatch('setInstanceOption', { - name: 'uploadlimit', + useInstanceStore().set({ + name: 'limits.uploadlimit', value: parseInt(uploadLimits.general), }) - store.dispatch('setInstanceOption', { - name: 'avatarlimit', + useInstanceStore().set({ + name: 'limits.avatarlimit', value: parseInt(uploadLimits.avatar), }) - store.dispatch('setInstanceOption', { - name: 'backgroundlimit', + useInstanceStore().set({ + name: 'limits.backgroundlimit', value: parseInt(uploadLimits.background), }) - store.dispatch('setInstanceOption', { - name: 'bannerlimit', + useInstanceStore().set({ + name: 'limits.bannerlimit', value: parseInt(uploadLimits.banner), }) - store.dispatch('setInstanceOption', { - name: 'fieldsLimits', + useInstanceStore().set({ + name: 'limits.fieldsLimits', value: metadata.fieldsLimits, }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'restrictedNicknames', value: metadata.restrictedNicknames, }) - store.dispatch('setInstanceOption', { - name: 'postFormats', + useInstanceStore().set({ + name: 'featureSet.postFormats', value: metadata.postFormats, }) const suggestions = metadata.suggestions - store.dispatch('setInstanceOption', { - name: 'suggestionsEnabled', + useInstanceStore().set({ + name: 'featureSet.suggestionsEnabled', value: suggestions.enabled, }) - store.dispatch('setInstanceOption', { - name: 'suggestionsWeb', + useInstanceStore().set({ + name: 'featureSet.suggestionsWeb', value: suggestions.web, }) const software = data.software - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'backendVersion', value: software.version, }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'backendRepository', value: software.repository, }) const priv = metadata.private - store.dispatch('setInstanceOption', { name: 'private', value: priv }) + useInstanceStore().set({ name: 'private', value: priv }) const frontendVersion = window.___pleromafe_commit_hash - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'frontendVersion', value: frontendVersion, }) const federation = metadata.federation - store.dispatch('setInstanceOption', { - name: 'tagPolicyAvailable', + useInstanceStore().set({ + name: 'featureSet.tagPolicyAvailable', value: typeof federation.mrf_policies === 'undefined' ? false : metadata.federation.mrf_policies.includes('TagPolicy'), }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'federationPolicy', value: federation, }) - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'federating', value: typeof federation.enabled === 'undefined' ? true : federation.enabled, }) const accountActivationRequired = metadata.accountActivationRequired - store.dispatch('setInstanceOption', { + useInstanceStore().set({ name: 'accountActivationRequired', value: accountActivationRequired, }) @@ -526,7 +531,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({ name: 'server', value: server }) await setConfig({ store }) try { diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 24794640e..dd595d2f5 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -131,7 +131,7 @@ const EmojiInput = { }, computed: { padEmoji() { - return this.$store.getters.mergedConfig.padEmoji + return useEmojiStore().mergedConfig.padEmoji }, defaultCandidateIndex() { return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1 diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index 79d97cff7..c478fea67 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -2,7 +2,7 @@ * 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 b267af935..f28cceffe 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -3,6 +3,7 @@ import { defineAsyncComponent } from 'vue' import Popover from 'src/components/popover/popover.vue' import { useInstanceStore } from 'src/stores/instance.js' +import { useEmojiStore } from 'src/stores/emoji.js' import { ensureFinalFallback } from '../../i18n/languages.js' import Checkbox from '../checkbox/checkbox.vue' import StillImage from '../still-image/still-image.vue' @@ -359,7 +360,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') } @@ -369,7 +370,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], diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 6fd4c9fab..667472148 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -7,6 +7,7 @@ import Gallery from 'src/components/gallery/gallery.vue' import Popover from 'src/components/popover/popover.vue' import { pollFormToMasto } from 'src/services/poll/poll.service.js' import { useInstanceStore } from 'src/stores/instance.js' +import { useEmojiStore } from 'src/stores/emoji.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMediaViewerStore } from 'src/stores/media_viewer.js' import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' @@ -259,8 +260,8 @@ const PostStatusForm = { emojiUserSuggestor() { return suggestor({ emoji: [ - ...this.$store.getters.standardEmojiList, - ...useInstanceStore().customEmoji, + ...useEmojiStore().standardEmojiList, + ...useEmojiStore().customEmoji, ], store: this.$store, }) @@ -268,16 +269,16 @@ const PostStatusForm = { emojiSuggestor() { return suggestor({ emoji: [ - ...this.$store.getters.standardEmojiList, - ...useInstanceStore().customEmoji, + ...useEmojiStore().standardEmojiList, + ...useEmojiStore().customEmoji, ], }) }, emoji() { - return this.$store.getters.standardEmojiList || [] + return useEmojiStore().standardEmojiList || [] }, customEmoji() { - return useInstanceStore().customEmoji || [] + return useEmojiStore().customEmoji || [] }, statusLength() { return this.newStatus.status.length diff --git a/src/modules/config.js b/src/modules/config.js index cf144664a..280ae1667 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -3,6 +3,8 @@ import { set } from 'lodash' import { useI18nStore } from 'src/stores/i18n.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useInstanceStore } from 'src/stores/instance.js' +import { useEmojiStore } from 'src/stores/emoji.js' import messages from '../i18n/messages' import localeService from '../services/locale/locale.service.js' import { applyConfig } from '../services/style_setter/style_setter.js' @@ -43,24 +45,23 @@ export const instanceDefaultProperties = Object.keys(instanceDefaultConfig) const config = { state: { ...defaultState }, getters: { - defaultConfig(state, getters, rootState) { - const { instance } = rootState + defaultConfig() { return { ...defaultState, ...Object.fromEntries( - instanceDefaultProperties.map((key) => [key, instance[key]]), + instanceDefaultProperties.map((key) => [key, useInstanceStore()[key]]), ), } }, - mergedConfig(state, getters, rootState, rootGetters) { - const { defaultConfig } = rootGetters - return { - ...defaultConfig, - // Do not override with undefined - ...Object.fromEntries( - Object.entries(state).filter(([, v]) => v !== undefined), - ), - } + mergedConfig(state) { + const instancePrefs = useInstanceStore().prefsStorage + const result = Object.fromEntries( + Object.entries(defaultState).map(([k, v]) => [ + k, + v ?? instancePrefs[k], + ]), + ) + return result }, }, mutations: { @@ -177,7 +178,7 @@ const config = { } case 'interfaceLanguage': messages.setLanguage(useI18nStore().i18n, value) - dispatch('loadUnicodeEmojiData', value) + useEmojiStore().loadUnicodeEmojiData(value) Cookies.set( BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocaleMulti(value), diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index d5daef3d3..805928bd4 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -18,7 +18,7 @@ export const staticOrApiConfigDefault = { redirectRootLogin: '/main/friends', redirectRootNoLogin: '/main/all', hideSitename: false, - nsfwCensorImage: undefined, + nsfwCensorImage: null, showFeaturesPanel: true, showInstanceSpecificPanel: false, } diff --git a/src/modules/index.js b/src/modules/index.js index ab9dd2418..15e57f716 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -3,14 +3,12 @@ import api from './api.js' import chats from './chats.js' import config from './config.js' import drafts from './drafts.js' -import instance from './instance.js' import notifications from './notifications.js' import profileConfig from './profileConfig.js' import statuses from './statuses.js' import users from './users.js' export default { - instance, statuses, notifications, users, diff --git a/src/modules/users.js b/src/modules/users.js index 6b2a26ae5..d9e36d42b 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -12,6 +12,7 @@ import { import { declarations } from 'src/modules/config_declaration' import { useInstanceStore } from 'src/stores/instance.js' import { useInterfaceStore } from 'src/stores/interface.js' +import { useEmojiStore } from 'src/stores/emoji.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import apiService from '../services/api/api.service.js' @@ -702,7 +703,7 @@ const users = { useServerSideStorageStore().setServerSideStorage(user) commit('addNewUsers', [user]) - dispatch('fetchEmoji') + useEmojiStore().fetchEmoji() getNotificationPermission().then((permission) => useInterfaceStore().setNotificationPermission(permission), diff --git a/src/stores/emoji.js b/src/stores/emoji.js new file mode 100644 index 000000000..e2e5b48f5 --- /dev/null +++ b/src/stores/emoji.js @@ -0,0 +1,246 @@ +import { defineStore } from 'pinia' + +import { useInstanceStore } from 'src/stores/instance.js' + +import { ensureFinalFallback } from 'src/i18n/languages.js' + +import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations' + +const defaultState = { + // Custom emoji from server + customEmoji: [], + customEmojiFetched: false, + + // Unicode emoji from bundle + emoji: {}, + emojiFetched: false, + unicodeEmojiAnnotations: {}, +} + +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 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 useEmojiStore = defineStore('emoji', { + state: () => ({ ...defaultState }), + getters: { + 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), + ), + })) + }, + }, + actions: { + 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 + ? useInstanceStore().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.getCustomEmoji().then(() => (this.customEmojiFetched = true)) + } + if (!this.emojiFetched) { + this.getStaticEmoji().then(() => (this.emojiFetched = true)) + } + }, + }, +}) diff --git a/src/stores/instance.js b/src/stores/instance.js index 3dc591f8f..9c10f3c0e 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -129,10 +129,10 @@ export const useInstanceStore = defineStore('instance', { }, }, actions: { - set({ path, value }) { - if (get(defaultState, path) === undefined) - console.error(`Unknown instance option ${path}, value: ${value}`) - set(this, path, value) + set({ path, name, value }) { + if (get(defaultState, path ?? name) === undefined) + console.error(`Unknown instance option ${path ?? name}, value: ${value}`) + set(this, path ?? name, value) switch (name) { case 'name': useInterfaceStore().setPageTitle() diff --git a/src/stores/interface.js b/src/stores/interface.js index 289fd845b..479948d55 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -87,7 +87,7 @@ export const useInterfaceStore = defineStore('interface', { }, setPageTitle(option = '') { try { - document.title = `${option} ${window.vuex.useInstanceStore().name}` + document.title = `${option} ${useInstanceStore().name}` } catch (error) { console.error(`${error}`) } @@ -222,14 +222,14 @@ export const useInterfaceStore = defineStore('interface', { async fetchPalettesIndex() { try { const value = await getResourcesIndex('/static/palettes/index.json') - window.vuex.commit('setInstanceOption', { + useInstanceStore().set({ name: 'palettesIndex', value, }) return value } catch (e) { console.error('Could not fetch palettes index', e) - window.vuex.commit('setInstanceOption', { + useInstanceStore().set({ name: 'palettesIndex', value: { _error: e }, }) @@ -258,11 +258,11 @@ export const useInterfaceStore = defineStore('interface', { '/static/styles/index.json', deserialize, ) - window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value }) + useInstanceStore().set({ name: 'stylesIndex', value }) return value } catch (e) { console.error('Could not fetch styles index', e) - window.vuex.commit('setInstanceOption', { + useInstanceStore().set({ name: 'stylesIndex', value: { _error: e }, }) @@ -296,11 +296,11 @@ export const useInterfaceStore = defineStore('interface', { async fetchThemesIndex() { try { const value = await getResourcesIndex('/static/styles.json') - window.vuex.commit('setInstanceOption', { name: 'themesIndex', value }) + useInstanceStore().set({ name: 'themesIndex', value }) return value } catch (e) { console.error('Could not fetch themes index', e) - window.vuex.commit('setInstanceOption', { + useInstanceStore().set({ name: 'themesIndex', value: { _error: e }, }) @@ -386,14 +386,14 @@ export const useInterfaceStore = defineStore('interface', { } const { style: instanceStyleName, palette: instancePaletteName } = - window.vuex.useInstanceStore() + useInstanceStore() let { theme: instanceThemeV2Name, themesIndex, stylesIndex, palettesIndex, - } = window.vuex.useInstanceStore() + } = useInstanceStore() const { style: userStyleName,