pass 2 + emoji store separation

This commit is contained in:
Henry Jameson 2026-01-29 01:45:31 +02:00
commit 4156b1597a
12 changed files with 372 additions and 119 deletions

View file

@ -22,7 +22,8 @@ import {
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useAuthFlowStore } from 'src/stores/auth_flow' import { useAuthFlowStore } from 'src/stores/auth_flow'
import { useI18nStore } from 'src/stores/i18n' 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 { useOAuthStore } from 'src/stores/oauth'
import App from '../App.vue' import App from '../App.vue'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' 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 textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'pleromaExtensionsAvailable', name: 'featureSet.pleromaExtensionsAvailable',
value: data.pleroma, value: data.pleroma,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'textlimit', name: 'textlimit',
value: textlimit, value: textlimit,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'accountApprovalRequired', name: 'accountApprovalRequired',
value: data.approval_required, value: data.approval_required,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'birthdayRequired', name: 'birthdayRequired',
value: !!data.pleroma?.metadata.birthday_required, value: !!data.pleroma?.metadata.birthday_required,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'birthdayMinAge', name: 'birthdayMinAge',
value: data.pleroma?.metadata.birthday_min_age || 0, value: data.pleroma?.metadata.birthday_min_age || 0,
}) })
if (vapidPublicKey) { if (vapidPublicKey) {
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'vapidPublicKey', name: 'vapidPublicKey',
value: vapidPublicKey, value: vapidPublicKey,
}) })
@ -161,14 +162,14 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
config = Object.assign({}, staticConfig, apiConfig) config = Object.assign({}, staticConfig, apiConfig)
} }
const copyInstanceOption = (name) => { const copyInstanceOption = (path) => {
if (typeof config[name] !== 'undefined') { 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(staticOrApiConfigDefault).map(k => `instanceIdentity.${k}`).forEach(copyInstanceOption)
Object.keys(instanceDefaultConfig).forEach(copyInstanceOption) Object.keys(instanceDefaultConfig).map(k => `prefsStorage.${k}`).forEach(copyInstanceOption)
useAuthFlowStore().setInitialStrategy(config.loginMethod) useAuthFlowStore().setInitialStrategy(config.loginMethod)
} }
@ -178,7 +179,7 @@ const getTOS = async ({ store }) => {
const res = await window.fetch('/static/terms-of-service.html') const res = await window.fetch('/static/terms-of-service.html')
if (res.ok) { if (res.ok) {
const html = await res.text() const html = await res.text()
store.dispatch('setInstanceOption', { name: 'tos', value: html }) useInstanceStore().set({ name: 'tos', value: html })
} else { } else {
throw res throw res
} }
@ -192,7 +193,7 @@ const getInstancePanel = async ({ store }) => {
const res = await preloadFetch('/instance/panel.html') const res = await preloadFetch('/instance/panel.html')
if (res.ok) { if (res.ok) {
const html = await res.text() const html = await res.text()
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'instanceSpecificPanelContent', name: 'instanceSpecificPanelContent',
value: html, value: html,
}) })
@ -227,7 +228,7 @@ const getStickers = async ({ store }) => {
).sort((a, b) => { ).sort((a, b) => {
return a.meta.title.localeCompare(b.meta.title) return a.meta.title.localeCompare(b.meta.title)
}) })
store.dispatch('setInstanceOption', { name: 'stickers', value: stickers }) useInstanceStore().set({ name: 'stickers', value: stickers })
} else { } else {
throw res throw res
} }
@ -248,7 +249,7 @@ const getAppSecret = async ({ store }) => {
const resolveStaffAccounts = ({ store, accounts }) => { const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map((uri) => uri.split('/').pop()) const nicknames = accounts.map((uri) => uri.split('/').pop())
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'staffAccounts', name: 'staffAccounts',
value: nicknames, value: nicknames,
}) })
@ -262,159 +263,163 @@ const getNodeInfo = async ({ store }) => {
const data = await res.json() const data = await res.json()
const metadata = data.metadata const metadata = data.metadata
const features = metadata.features const features = metadata.features
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'name', path: 'name',
value: metadata.nodeName, value: metadata.nodeName,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'registrationOpen', path: 'registrationOpen',
value: data.openRegistrations, value: data.openRegistrations,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'mediaProxyAvailable', path: 'featureSet.mediaProxyAvailable',
value: features.includes('media_proxy'), value: features.includes('media_proxy'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'safeDM', path: 'featureSet.safeDM',
value: features.includes('safe_dm_mentions'), value: features.includes('safe_dm_mentions'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'shoutAvailable', path: 'featureSet.shoutAvailable',
value: features.includes('chat'), value: features.includes('chat'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'pleromaChatMessagesAvailable', path: 'featureSet.pleromaChatMessagesAvailable',
value: features.includes('pleroma_chat_messages'), value: features.includes('pleroma_chat_messages'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'pleromaCustomEmojiReactionsAvailable', path: 'featureSet.pleromaCustomEmojiReactionsAvailable',
value: value:
features.includes('pleroma_custom_emoji_reactions') || features.includes('pleroma_custom_emoji_reactions') ||
features.includes('custom_emoji_reactions'), features.includes('custom_emoji_reactions'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'pleromaBookmarkFoldersAvailable', path: 'featureSet.pleromaBookmarkFoldersAvailable',
value: features.includes('pleroma:bookmark_folders'), value: features.includes('pleroma:bookmark_folders'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'gopherAvailable', path: 'featureSet.gopherAvailable',
value: features.includes('gopher'), value: features.includes('gopher'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'pollsAvailable', path: 'featureSet.pollsAvailable',
value: features.includes('polls'), value: features.includes('polls'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'editingAvailable', path: 'featureSet.editingAvailable',
value: features.includes('editing'), value: features.includes('editing'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'pollLimits', path: 'featureSet.mailerEnabled',
value: metadata.pollLimits,
})
store.dispatch('setInstanceOption', {
name: 'mailerEnabled',
value: metadata.mailerEnabled, value: metadata.mailerEnabled,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'quotingAvailable', path: 'featureSet.quotingAvailable',
value: features.includes('quote_posting'), value: features.includes('quote_posting'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'groupActorAvailable', path: 'featureSet.groupActorAvailable',
value: features.includes('pleroma:group_actors'), value: features.includes('pleroma:group_actors'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'blockExpiration', path: 'featureSet.blockExpiration',
value: features.includes('pleroma:block_expiration'), value: features.includes('pleroma:block_expiration'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'localBubbleInstances', path: 'localBubbleInstances',
value: metadata.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 const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'uploadlimit', name: 'limits.uploadlimit',
value: parseInt(uploadLimits.general), value: parseInt(uploadLimits.general),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'avatarlimit', name: 'limits.avatarlimit',
value: parseInt(uploadLimits.avatar), value: parseInt(uploadLimits.avatar),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'backgroundlimit', name: 'limits.backgroundlimit',
value: parseInt(uploadLimits.background), value: parseInt(uploadLimits.background),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'bannerlimit', name: 'limits.bannerlimit',
value: parseInt(uploadLimits.banner), value: parseInt(uploadLimits.banner),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'fieldsLimits', name: 'limits.fieldsLimits',
value: metadata.fieldsLimits, value: metadata.fieldsLimits,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'restrictedNicknames', name: 'restrictedNicknames',
value: metadata.restrictedNicknames, value: metadata.restrictedNicknames,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'postFormats', name: 'featureSet.postFormats',
value: metadata.postFormats, value: metadata.postFormats,
}) })
const suggestions = metadata.suggestions const suggestions = metadata.suggestions
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'suggestionsEnabled', name: 'featureSet.suggestionsEnabled',
value: suggestions.enabled, value: suggestions.enabled,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'suggestionsWeb', name: 'featureSet.suggestionsWeb',
value: suggestions.web, value: suggestions.web,
}) })
const software = data.software const software = data.software
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'backendVersion', name: 'backendVersion',
value: software.version, value: software.version,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'backendRepository', name: 'backendRepository',
value: software.repository, value: software.repository,
}) })
const priv = metadata.private const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv }) useInstanceStore().set({ name: 'private', value: priv })
const frontendVersion = window.___pleromafe_commit_hash const frontendVersion = window.___pleromafe_commit_hash
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'frontendVersion', name: 'frontendVersion',
value: frontendVersion, value: frontendVersion,
}) })
const federation = metadata.federation const federation = metadata.federation
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'tagPolicyAvailable', name: 'featureSet.tagPolicyAvailable',
value: value:
typeof federation.mrf_policies === 'undefined' typeof federation.mrf_policies === 'undefined'
? false ? false
: metadata.federation.mrf_policies.includes('TagPolicy'), : metadata.federation.mrf_policies.includes('TagPolicy'),
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'federationPolicy', name: 'federationPolicy',
value: federation, value: federation,
}) })
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'federating', name: 'federating',
value: value:
typeof federation.enabled === 'undefined' ? true : federation.enabled, typeof federation.enabled === 'undefined' ? true : federation.enabled,
}) })
const accountActivationRequired = metadata.accountActivationRequired const accountActivationRequired = metadata.accountActivationRequired
store.dispatch('setInstanceOption', { useInstanceStore().set({
name: 'accountActivationRequired', name: 'accountActivationRequired',
value: accountActivationRequired, value: accountActivationRequired,
}) })
@ -526,7 +531,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
typeof overrides.target !== 'undefined' typeof overrides.target !== 'undefined'
? overrides.target ? overrides.target
: window.location.origin : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server }) useInstanceStore().set({ name: 'server', value: server })
await setConfig({ store }) await setConfig({ store })
try { try {

View file

@ -131,7 +131,7 @@ const EmojiInput = {
}, },
computed: { computed: {
padEmoji() { padEmoji() {
return this.$store.getters.mergedConfig.padEmoji return useEmojiStore().mergedConfig.padEmoji
}, },
defaultCandidateIndex() { defaultCandidateIndex() {
return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1 return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1

View file

@ -2,7 +2,7 @@
* suggest - generates a suggestor function to be used by emoji-input * suggest - generates a suggestor function to be used by emoji-input
* data: object providing source information for specific types of suggestions: * data: object providing source information for specific types of suggestions:
* data.emoji - optional, an array of all emoji available i.e. * 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 * data.users - optional, an array of all known users
* updateUsersList - optional, a function to search and append to users * updateUsersList - optional, a function to search and append to users
* *

View file

@ -3,6 +3,7 @@ import { defineAsyncComponent } from 'vue'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useEmojiStore } from 'src/stores/emoji.js'
import { ensureFinalFallback } from '../../i18n/languages.js' import { ensureFinalFallback } from '../../i18n/languages.js'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
@ -359,7 +360,7 @@ const EmojiPicker = {
if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) { if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) {
return {} return {}
} }
const emojis = this.$store.getters.groupedCustomEmojis const emojis = useEmojiStore().groupedCustomEmojis
if (emojis.unpacked) { if (emojis.unpacked) {
emojis.unpacked.text = this.$t('emoji.unpacked') emojis.unpacked.text = this.$t('emoji.unpacked')
} }
@ -369,7 +370,7 @@ const EmojiPicker = {
return Object.keys(this.allCustomGroups)[0] return Object.keys(this.allCustomGroups)[0]
}, },
unicodeEmojiGroups() { unicodeEmojiGroups() {
return this.$store.getters.standardEmojiGroupList.map((group) => ({ return useEmojiStore().standardEmojiGroupList.map((group) => ({
id: `standard-${group.id}`, id: `standard-${group.id}`,
text: this.$t(`emoji.unicode_groups.${group.id}`), text: this.$t(`emoji.unicode_groups.${group.id}`),
icon: UNICODE_EMOJI_GROUP_ICON[group.id], icon: UNICODE_EMOJI_GROUP_ICON[group.id],

View file

@ -7,6 +7,7 @@ import Gallery from 'src/components/gallery/gallery.vue'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import { pollFormToMasto } from 'src/services/poll/poll.service.js' import { pollFormToMasto } from 'src/services/poll/poll.service.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMediaViewerStore } from 'src/stores/media_viewer.js' import { useMediaViewerStore } from 'src/stores/media_viewer.js'
import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js'
@ -259,8 +260,8 @@ const PostStatusForm = {
emojiUserSuggestor() { emojiUserSuggestor() {
return suggestor({ return suggestor({
emoji: [ emoji: [
...this.$store.getters.standardEmojiList, ...useEmojiStore().standardEmojiList,
...useInstanceStore().customEmoji, ...useEmojiStore().customEmoji,
], ],
store: this.$store, store: this.$store,
}) })
@ -268,16 +269,16 @@ const PostStatusForm = {
emojiSuggestor() { emojiSuggestor() {
return suggestor({ return suggestor({
emoji: [ emoji: [
...this.$store.getters.standardEmojiList, ...useEmojiStore().standardEmojiList,
...useInstanceStore().customEmoji, ...useEmojiStore().customEmoji,
], ],
}) })
}, },
emoji() { emoji() {
return this.$store.getters.standardEmojiList || [] return useEmojiStore().standardEmojiList || []
}, },
customEmoji() { customEmoji() {
return useInstanceStore().customEmoji || [] return useEmojiStore().customEmoji || []
}, },
statusLength() { statusLength() {
return this.newStatus.status.length return this.newStatus.status.length

View file

@ -3,6 +3,8 @@ import { set } from 'lodash'
import { useI18nStore } from 'src/stores/i18n.js' import { useI18nStore } from 'src/stores/i18n.js'
import { useInterfaceStore } from 'src/stores/interface.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 messages from '../i18n/messages'
import localeService from '../services/locale/locale.service.js' import localeService from '../services/locale/locale.service.js'
import { applyConfig } from '../services/style_setter/style_setter.js' import { applyConfig } from '../services/style_setter/style_setter.js'
@ -43,24 +45,23 @@ export const instanceDefaultProperties = Object.keys(instanceDefaultConfig)
const config = { const config = {
state: { ...defaultState }, state: { ...defaultState },
getters: { getters: {
defaultConfig(state, getters, rootState) { defaultConfig() {
const { instance } = rootState
return { return {
...defaultState, ...defaultState,
...Object.fromEntries( ...Object.fromEntries(
instanceDefaultProperties.map((key) => [key, instance[key]]), instanceDefaultProperties.map((key) => [key, useInstanceStore()[key]]),
), ),
} }
}, },
mergedConfig(state, getters, rootState, rootGetters) { mergedConfig(state) {
const { defaultConfig } = rootGetters const instancePrefs = useInstanceStore().prefsStorage
return { const result = Object.fromEntries(
...defaultConfig, Object.entries(defaultState).map(([k, v]) => [
// Do not override with undefined k,
...Object.fromEntries( v ?? instancePrefs[k],
Object.entries(state).filter(([, v]) => v !== undefined), ]),
), )
} return result
}, },
}, },
mutations: { mutations: {
@ -177,7 +178,7 @@ const config = {
} }
case 'interfaceLanguage': case 'interfaceLanguage':
messages.setLanguage(useI18nStore().i18n, value) messages.setLanguage(useI18nStore().i18n, value)
dispatch('loadUnicodeEmojiData', value) useEmojiStore().loadUnicodeEmojiData(value)
Cookies.set( Cookies.set(
BACKEND_LANGUAGE_COOKIE_NAME, BACKEND_LANGUAGE_COOKIE_NAME,
localeService.internalToBackendLocaleMulti(value), localeService.internalToBackendLocaleMulti(value),

View file

@ -18,7 +18,7 @@ export const staticOrApiConfigDefault = {
redirectRootLogin: '/main/friends', redirectRootLogin: '/main/friends',
redirectRootNoLogin: '/main/all', redirectRootNoLogin: '/main/all',
hideSitename: false, hideSitename: false,
nsfwCensorImage: undefined, nsfwCensorImage: null,
showFeaturesPanel: true, showFeaturesPanel: true,
showInstanceSpecificPanel: false, showInstanceSpecificPanel: false,
} }

View file

@ -3,14 +3,12 @@ import api from './api.js'
import chats from './chats.js' import chats from './chats.js'
import config from './config.js' import config from './config.js'
import drafts from './drafts.js' import drafts from './drafts.js'
import instance from './instance.js'
import notifications from './notifications.js' import notifications from './notifications.js'
import profileConfig from './profileConfig.js' import profileConfig from './profileConfig.js'
import statuses from './statuses.js' import statuses from './statuses.js'
import users from './users.js' import users from './users.js'
export default { export default {
instance,
statuses, statuses,
notifications, notifications,
users, users,

View file

@ -12,6 +12,7 @@ import {
import { declarations } from 'src/modules/config_declaration' import { declarations } from 'src/modules/config_declaration'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
@ -702,7 +703,7 @@ const users = {
useServerSideStorageStore().setServerSideStorage(user) useServerSideStorageStore().setServerSideStorage(user)
commit('addNewUsers', [user]) commit('addNewUsers', [user])
dispatch('fetchEmoji') useEmojiStore().fetchEmoji()
getNotificationPermission().then((permission) => getNotificationPermission().then((permission) =>
useInterfaceStore().setNotificationPermission(permission), useInterfaceStore().setNotificationPermission(permission),

246
src/stores/emoji.js Normal file
View file

@ -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))
}
},
},
})

View file

@ -129,10 +129,10 @@ export const useInstanceStore = defineStore('instance', {
}, },
}, },
actions: { actions: {
set({ path, value }) { set({ path, name, value }) {
if (get(defaultState, path) === undefined) if (get(defaultState, path ?? name) === undefined)
console.error(`Unknown instance option ${path}, value: ${value}`) console.error(`Unknown instance option ${path ?? name}, value: ${value}`)
set(this, path, value) set(this, path ?? name, value)
switch (name) { switch (name) {
case 'name': case 'name':
useInterfaceStore().setPageTitle() useInterfaceStore().setPageTitle()

View file

@ -87,7 +87,7 @@ export const useInterfaceStore = defineStore('interface', {
}, },
setPageTitle(option = '') { setPageTitle(option = '') {
try { try {
document.title = `${option} ${window.vuex.useInstanceStore().name}` document.title = `${option} ${useInstanceStore().name}`
} catch (error) { } catch (error) {
console.error(`${error}`) console.error(`${error}`)
} }
@ -222,14 +222,14 @@ export const useInterfaceStore = defineStore('interface', {
async fetchPalettesIndex() { async fetchPalettesIndex() {
try { try {
const value = await getResourcesIndex('/static/palettes/index.json') const value = await getResourcesIndex('/static/palettes/index.json')
window.vuex.commit('setInstanceOption', { useInstanceStore().set({
name: 'palettesIndex', name: 'palettesIndex',
value, value,
}) })
return value return value
} catch (e) { } catch (e) {
console.error('Could not fetch palettes index', e) console.error('Could not fetch palettes index', e)
window.vuex.commit('setInstanceOption', { useInstanceStore().set({
name: 'palettesIndex', name: 'palettesIndex',
value: { _error: e }, value: { _error: e },
}) })
@ -258,11 +258,11 @@ export const useInterfaceStore = defineStore('interface', {
'/static/styles/index.json', '/static/styles/index.json',
deserialize, deserialize,
) )
window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value }) useInstanceStore().set({ name: 'stylesIndex', value })
return value return value
} catch (e) { } catch (e) {
console.error('Could not fetch styles index', e) console.error('Could not fetch styles index', e)
window.vuex.commit('setInstanceOption', { useInstanceStore().set({
name: 'stylesIndex', name: 'stylesIndex',
value: { _error: e }, value: { _error: e },
}) })
@ -296,11 +296,11 @@ export const useInterfaceStore = defineStore('interface', {
async fetchThemesIndex() { async fetchThemesIndex() {
try { try {
const value = await getResourcesIndex('/static/styles.json') const value = await getResourcesIndex('/static/styles.json')
window.vuex.commit('setInstanceOption', { name: 'themesIndex', value }) useInstanceStore().set({ name: 'themesIndex', value })
return value return value
} catch (e) { } catch (e) {
console.error('Could not fetch themes index', e) console.error('Could not fetch themes index', e)
window.vuex.commit('setInstanceOption', { useInstanceStore().set({
name: 'themesIndex', name: 'themesIndex',
value: { _error: e }, value: { _error: e },
}) })
@ -386,14 +386,14 @@ export const useInterfaceStore = defineStore('interface', {
} }
const { style: instanceStyleName, palette: instancePaletteName } = const { style: instanceStyleName, palette: instancePaletteName } =
window.vuex.useInstanceStore() useInstanceStore()
let { let {
theme: instanceThemeV2Name, theme: instanceThemeV2Name,
themesIndex, themesIndex,
stylesIndex, stylesIndex,
palettesIndex, palettesIndex,
} = window.vuex.useInstanceStore() } = useInstanceStore()
const { const {
style: userStyleName, style: userStyleName,