Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
36 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
index 2c7b151be9..102d231ba9 100644
--- a/src/components/settings_modal/helpers/emoji_editing_popover.vue
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -1,351 +1,351 @@
<template>
<Popover
ref="emojiPopover"
trigger="click"
:placement="placement"
bound-to-selector=".emoji-list"
popover-class="emoji-tab-edit-popover popover-default"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
:class="{'emoji-unsaved': isEdited}"
>
<template #trigger>
<slot name="trigger" />
</template>
<template #content>
<h3>
{{ title }}
</h3>
<StillImage
v-if="emojiPreview"
class="emoji"
:src="emojiPreview"
/>
<div
v-else
class="emoji"
/>
<div
v-if="newUpload"
class="emoji-tab-popover-new-upload"
>
<h4>{{ $t('admin_dash.emoji.emoji_source') }}</h4>
<div class="emoji-tab-popover-input">
<input
type="file"
accept="image/*"
class="emoji-tab-popover-file input"
@change="uploadFile = $event.target.files"
>
</div>
<div class="emoji-tab-popover-input ">
<input
v-model="uploadURL"
:placeholder="$t('admin_dash.emoji.upload_url')"
class="emoji-data-input input"
>
</div>
</div>
<div>
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.shortcode') }}
<input
v-model="editedShortcode"
class="emoji-data-input input"
:placeholder="$t('admin_dash.emoji.new_shortcode')"
>
</label>
</div>
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.filename') }}
<input
v-model="editedFile"
class="emoji-data-input input"
:placeholder="$t('admin_dash.emoji.new_filename')"
>
</label>
</div>
<div
v-if="remote !== undefined"
class="emoji-tab-popover-input"
>
<label>
{{ $t('admin_dash.emoji.copy_to') }}
<SelectComponent
v-model="copyToPack"
class="form-control"
>
<option
value=""
disabled
hidden
>
{{ $t('admin_dash.emoji.emoji_pack') }}
</option>
<option
v-for="(pack, listPackName) in knownLocalPacks"
:key="listPackName"
:label="listPackName"
>
{{ listPackName }}
</option>
</SelectComponent>
</label>
</div>
<!--
For local emojis, disable the button if nothing was edited.
For remote emojis, also disable it if a local pack is not selected.
Remote emojis are processed by the same function that uploads new ones, as that is effectively what it does
-->
<button
class="button button-default btn"
type="button"
:disabled="saveButtonDisabled"
@click="(newUpload || remote !== undefined) ? uploadEmoji() : saveEditedEmoji()"
>
{{ $t('admin_dash.emoji.save') }}
</button>
<template v-if="!newUpload && remote === undefined">
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="deleteModalVisible = true"
>
{{ $t('admin_dash.emoji.delete') }}
</button>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="revertEmoji"
>
{{ $t('admin_dash.emoji.revert') }}
</button>
<ConfirmModal
v-if="deleteModalVisible"
:title="$t('admin_dash.emoji.delete_title')"
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
@accepted="deleteEmoji"
>
{{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
</ConfirmModal>
</template>
</div>
</template>
</Popover>
</template>
<script>
import Popover from 'components/popover/popover.vue'
import SelectComponent from 'components/select/select.vue'
import { defineAsyncComponent } from 'vue'
import { useCredentialsStore } from 'src/stores/credentials.js'
import {
addNewEmojiFile,
deleteEmojiFile,
updateEmojiFile,
-} from 'src/services/api/api.service.js'
+} from 'src/services/api/admin.js'
export default {
components: {
Popover,
ConfirmModal: defineAsyncComponent(
() => import('src/components/confirm_modal/confirm_modal.vue'),
),
SelectComponent,
},
inject: ['emojiAddr'],
props: {
placement: {
type: String,
required: true,
},
newUpload: Boolean,
title: {
type: String,
required: true,
},
packName: {
type: String,
required: true,
},
shortcode: {
type: String,
// Only exists when this is not a new upload
default: '',
},
file: {
type: String,
// Only exists when this is not a new upload
default: '',
},
// Only exists for emojis from remote packs
remote: {
type: Object,
default: undefined,
},
knownLocalPacks: {
type: Object,
default: undefined,
},
},
emits: ['updatePackFiles', 'displayError'],
data() {
return {
uploadFile: [],
uploadURL: '',
editedShortcode: this.shortcode,
editedFile: this.file,
deleteModalVisible: false,
copyToPack: '',
}
},
computed: {
emojiPreview() {
if (this.newUpload && this.uploadFile.length > 0) {
return URL.createObjectURL(this.uploadFile[0])
} else if (this.newUpload && this.uploadURL !== '') {
return this.uploadURL
} else if (!this.newUpload) {
return this.emojiAddr(this.file)
}
return null
},
isEdited() {
return (
!this.newUpload &&
(this.editedShortcode !== this.shortcode ||
this.editedFile !== this.file)
)
},
saveButtonDisabled() {
if (this.remote === undefined)
return this.newUpload
? this.uploadURL === '' && this.uploadFile.length == 0
: !this.isEdited
else return this.copyToPack === ''
},
},
methods: {
saveEditedEmoji() {
if (!this.isEdited) return
updateEmojiFile({
packName: this.packName,
shortcode: this.shortcode,
newShortcode: this.editedShortcode,
newFilename: this.editedFile,
force: false,
credentials: useCredentialsStore().current,
})
.then((resp) => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return Promise.reject(resp.error)
}
return resp.json()
})
.then((resp) => this.$emit('updatePackFiles', resp))
},
uploadEmoji() {
let packName = this.remote === undefined ? this.packName : this.copyToPack
addNewEmojiFile({
packName: packName,
file:
this.remote === undefined
? this.uploadURL !== ''
? this.uploadURL
: this.uploadFile[0]
: this.emojiAddr(this.file),
shortcode: this.editedShortcode,
filename: this.editedFile,
credentials: useCredentialsStore().current,
})
.then((resp) => resp.json())
.then((resp) => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return
}
this.$emit('updatePackFiles', resp, packName)
this.$refs.emojiPopover.hidePopover()
this.editedFile = ''
this.editedShortcode = ''
this.uploadFile = []
})
},
revertEmoji() {
this.editedFile = this.file
this.editedShortcode = this.shortcode
},
deleteEmoji() {
this.deleteModalVisible = false
deleteEmojiFile({
packName: this.packName,
shortcode: this.shortcode,
credentials: useCredentialsStore().current,
})
.then((resp) => resp.json())
.then((resp) => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return
}
this.$emit('updatePackFiles', resp, this.packName)
})
},
},
}
</script>
<style lang="scss">
.emoji-tab-edit-popover {
padding-left: 0.5em;
padding-right: 0.5em;
padding-bottom: 0.5em;
.emoji-tab-popover-new-upload {
margin-bottom: 2em;
}
.emoji {
width: 2.3em;
height: 2.3em;
}
.Select {
display: inline-block;
}
h4 {
margin-bottom: 1em;
margin-top: 1em;
}
}
</style>
diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js
index d76c5f6d5a..1ced1a1c13 100644
--- a/src/stores/admin_settings.js
+++ b/src/stores/admin_settings.js
@@ -1,603 +1,607 @@
import { cloneDeep, differenceWith, flatten, get, isEqual, set } from 'lodash'
import { defineStore } from 'pinia'
import { useCredentialsStore } from 'src/stores/credentials.js'
import {
addNewEmojiFile,
changeStatusScope,
createEmojiPack,
deleteAccounts,
deleteEmojiPack,
disableMFA,
downloadRemoteEmojiPack,
downloadRemoteEmojiPackZIP,
getAvailableFrontends,
getInstanceConfigDescriptions,
getInstanceDBConfig,
getUserData,
importEmojiFromFS,
installFrontend,
listEmojiPacks,
listRemoteEmojiPacks,
listStatuses,
listUsers,
pushInstanceDBConfig,
reloadEmoji,
requirePasswordChange,
resendConfirmationEmail,
setUsersActivationStatus,
setUsersApprovalStatus,
setUsersConfirmationStatus,
setUsersRight,
setUsersSuggestionStatus,
setUsersTags,
} from 'src/services/api/admin.js'
import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js'
export const defaultState = {
frontends: [],
loaded: false,
needsReboot: null,
config: null,
modifiedPaths: null,
descriptions: null,
draft: null,
dbConfigEnabled: null,
}
export const newUserFlags = {
...defaultState.flagStorage,
}
export const useAdminSettingsStore = defineStore('adminSettings', {
state: () => ({
...cloneDeep(defaultState),
}),
actions: {
// Configuration Stuff
setInstanceAdminNoDbConfig() {
this.loaded = false
this.dbConfigEnabled = false
},
updateAdminSettings({ config, modifiedPaths }) {
this.loaded = true
this.dbConfigEnabled = true
this.config = config
this.modifiedPaths = modifiedPaths
},
updateAdminDescriptions({ descriptions }) {
this.descriptions = descriptions
},
updateAdminDraft({ path, value }) {
const [group, key, subkey] = path
const parent = [group, key, subkey]
set(this.draft, path, value)
// force-updating grouped draft to trigger refresh of group settings
if (path.length > parent.length) {
set(this.draft, parent, cloneDeep(get(this.draft, parent)))
}
},
resetAdminDraft() {
this.draft = cloneDeep(this.config)
},
loadAdminStuff() {
getInstanceDBConfig({
credentials: useCredentialsStore().current,
}).then((backendDbConfig) => {
if (backendDbConfig.error) {
if (backendDbConfig.error.status === 400) {
backendDbConfig.error.json().then((errorJson) => {
if (/configurable_from_database/.test(errorJson.error)) {
this.setInstanceAdminNoDbConfig()
}
})
}
} else {
this.setInstanceAdminSettings({
credentials: useCredentialsStore().current,
backendDbConfig,
})
}
})
if (this.descriptions === null) {
getInstanceConfigDescriptions({
credentials: useCredentialsStore().current,
}).then((backendDescriptions) =>
this.setInstanceAdminDescriptions({
credentials: useCredentialsStore().current,
backendDescriptions,
}),
)
}
},
setInstanceAdminSettings({ backendDbConfig }) {
const config = this.config || {}
const modifiedPaths = new Set()
backendDbConfig.configs.forEach((c) => {
const path = [c.group, c.key]
if (c.db) {
// Path elements can contain dot, therefore we use ' -> ' as a separator instead
// Using strings for modified paths for easier searching
c.db.forEach((x) => modifiedPaths.add([...path, x].join(' -> ')))
}
// we need to preserve tuples on second level only, possibly third
// but it's not a case right now.
const convert = (value, preserveTuples, preserveTuplesLv2) => {
if (Array.isArray(value) && value.length > 0 && value[0].tuple) {
if (!preserveTuples) {
return value.reduce((acc, c) => {
if (c.tuple == null) {
return {
...acc,
[c]: c,
}
}
return {
...acc,
[c.tuple[0]]: convert(c.tuple[1], preserveTuplesLv2),
}
}, {})
} else {
return value.map((x) => x.tuple)
}
} else {
if (!preserveTuples) {
return value
} else {
return value.tuple
}
}
}
// for most stuff we want maps since those are more convenient
// however this doesn't allow for multiple values per same key
// so for those cases we want to preserve tuples as-is
// right now it's made exclusively for :pleroma.:rate_limit
// so it might not work properly elsewhere
const preserveTuples = path.find((x) => x === ':rate_limit')
set(config, path, convert(c.value, false, preserveTuples))
})
// patching http adapter config to be easier to handle
const adapter = config[':pleroma'][':http'][':adapter']
if (Array.isArray(adapter)) {
config[':pleroma'][':http'][':adapter'] = {
[':ssl_options']: {
[':versions']: [],
},
}
}
this.updateAdminSettings({ config, modifiedPaths })
this.resetAdminDraft()
},
setInstanceAdminDescriptions({ backendDescriptions }) {
const convert = (
{ children, description, label, key = '<ROOT>', group, suggestions },
path,
acc,
) => {
const newPath = group ? [group, key] : [key]
const obj = { description, label, suggestions }
if (Array.isArray(children)) {
children.forEach((c) => {
convert(c, newPath, obj)
})
}
set(acc, newPath, obj)
}
const descriptions = {}
backendDescriptions.forEach((d) => convert(d, '', descriptions))
this.updateAdminDescriptions({ descriptions })
},
// This action takes draft state, diffs it with live config state and then pushes
// only differences between the two. Difference detection only work up to subkey (third) level.
pushAdminDraft() {
// TODO cleanup paths in modifiedPaths
const convert = (value) => {
if (typeof value !== 'object') {
return value
} else if (Array.isArray(value)) {
return value.map(convert)
} else {
return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
}
}
// Getting all group-keys used in config
const allGroupKeys = flatten(
Object.entries(this.config).map(([group, lv1data]) =>
Object.keys(lv1data).map((key) => ({ group, key })),
),
)
// Only using group-keys where there are changes detected
const changedGroupKeys = allGroupKeys.filter(({ group, key }) => {
return !isEqual(this.config[group][key], this.draft[group][key])
})
// Here we take all changed group-keys and get all changed subkeys
const changed = changedGroupKeys.map(({ group, key }) => {
const config = this.config[group][key]
const draft = this.draft[group][key]
// We convert group-key value into entries arrays
const eConfig = Object.entries(config)
const eDraft = Object.entries(draft)
// Then those entries array we diff so only changed subkey entries remain
// We use the diffed array to reconstruct the object and then shove it into convert()
return {
group,
key,
value: convert(
Object.fromEntries(differenceWith(eDraft, eConfig, isEqual)),
),
}
})
pushInstanceDBConfig({
credentials: useCredentialsStore().current,
payload: {
configs: changed,
},
})
.then(() =>
getInstanceDBConfig({
credentials: useCredentialsStore().current,
}),
)
.then((backendDbConfig) =>
this.setInstanceAdminSettings({
credentials: useCredentialsStore().current,
backendDbConfig,
}),
)
},
pushAdminSetting({ path, value }) {
const [group, key, ...rest] = Array.isArray(path)
? path
: path.split(/\./g)
const clone = {} // not actually cloning the entire thing to avoid excessive writes
set(clone, rest, value)
// TODO cleanup paths in modifiedPaths
const convert = (value) => {
if (typeof value !== 'object') {
return value
} else if (Array.isArray(value)) {
return value.map(convert)
} else {
return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
}
}
pushInstanceDBConfig({
credentials: useCredentialsStore().current,
payload: {
configs: [
{
group,
key,
value: convert(clone),
},
],
},
})
.then(() =>
getInstanceDBConfig({
credentials: useCredentialsStore().current,
}),
)
.then((backendDbConfig) =>
this.setInstanceAdminSettings({
credentials: useCredentialsStore().current,
backendDbConfig,
}),
)
},
resetAdminSetting({ path }) {
const [group, key, subkey] = Array.isArray(path)
? path
: path.split(/\./g)
this.modifiedPaths.delete(path)
return pushInstanceDBConfig({
credentials: useCredentialsStore().current,
payload: {
configs: [
{
group,
key,
delete: true,
subkeys: [subkey],
},
],
},
})
.then(() =>
getInstanceDBConfig({
credentials: useCredentialsStore().current,
}),
)
.then((backendDbConfig) =>
this.setInstanceAdminSettings({ backendDbConfig }),
)
},
// Frontends Stuff
loadFrontendsStuff() {
getAvailableFrontends({
credentials: useCredentialsStore().current,
}).then((frontends) => this.setAvailableFrontends({ frontends }))
},
setAvailableFrontends({ frontends }) {
this.frontends = frontends.map((f) => {
f.installedRefs = f.installed_refs
if (f.name === 'pleroma-fe') {
f.refs = ['master', 'develop']
} else {
f.refs = [f.ref]
}
return f
})
},
installFrontend() {
return installFrontend({
credentials: useCredentialsStore().current,
})
},
// Statuses stuff
async fetchStatuses(opts) {
const { total, activities } = await listStatuses({
credentials: useCredentialsStore().current,
opts,
})
const statuses = activities.map(parseStatus)
await window.vuex.dispatch('addNewStatuses', { statuses })
return {
items: statuses,
count: total,
}
},
async changeStatusScope(opts) {
const raw = await changeStatusScope({
credentials: useCredentialsStore().current,
opts,
})
const status = parseStatus(raw)
await window.vuex.dispatch('addNewStatuses', { statuses: [status] })
},
// Users stuff
async fetchUsers(opts) {
const { users, count } = await listUsers({
credentials: useCredentialsStore().current,
opts,
})
return {
items: await Promise.all(
users.map(
async (userAdminData) =>
await window.vuex.dispatch('updateUserAdminData', {
userAdminData,
}),
),
),
count,
}
},
async getUserData({ user }) {
const api = getUserData
const { screen_name } = user
const result = await api({
credentials: useCredentialsStore().current,
screen_name,
})
window.vuex.commit('updateUserAdminData', { user: result })
},
async deleteUsers({ users }) {
const screen_names = users.map((u) => u.screen_name)
const api = deleteAccounts
const resultUserIds = await api({
credentials: useCredentialsStore().current,
screen_names,
})
resultUserIds.forEach((userId) => {
window.vuex.dispatch(
'markStatusesAsDeleted',
(status) => userId === status.user.id,
)
// TODO when migrated to pinia, also remove user
})
return resultUserIds
},
resendConfirmationEmail({ users }) {
const screen_names = users.map((u) => u.screen_name)
return resendConfirmationEmail({
credentials: useCredentialsStore().current,
screen_names,
})
},
requirePasswordChange({ users }) {
const screen_names = users.map((u) => u.screen_name)
return requirePasswordChange({
credentials: useCredentialsStore().current,
screen_names,
})
},
// Singular only!
disableMFA({ user }) {
const { screen_name } = user
return disableMFA({
credentials: useCredentialsStore().current,
screen_name,
})
},
async setUsersTags({ users, tags, value }) {
const screen_names = users.map((u) => u.screen_name)
const api = setUsersTags
await api({
credentials: useCredentialsStore().current,
screen_names,
tags,
value,
})
users.forEach((user) => {
this.getUserData({ user })
})
},
async setUsersRight({ users, right, value }) {
const screen_names = users.map((u) => u.screen_name)
const api = setUsersRight
await api({
credentials: useCredentialsStore().current,
screen_names,
right,
value,
})
users.forEach((user) => {
window.vuex.commit('updateRight', { user, right, value })
})
},
async setUsersActivationStatus({ users, value }) {
const screen_names = users.map((u) => u.screen_name)
const api = setUsersActivationStatus
const resultUsers = await api({
credentials: useCredentialsStore().current,
screen_names,
value,
})
resultUsers.forEach((user) => {
window.vuex.commit('updateUserAdminData', { user })
})
},
async setUsersSuggestionStatus({ users, value }) {
const screen_names = users.map((u) => u.screen_name)
const api = setUsersSuggestionStatus
const resultUsers = await api({
credentials: useCredentialsStore().current,
screen_names,
value,
})
resultUsers.forEach((user) => {
window.vuex.commit('updateUserAdminData', { user })
})
},
async setUsersConfirmationStatus({ users }) {
const screen_names = users.map((u) => u.screen_name)
const api = setUsersConfirmationStatus
await api({
credentials: useCredentialsStore().current,
screen_names,
})
users.forEach((user) => {
this.getUserData({ user })
})
},
async setUsersApprovalStatus({ users }) {
const screen_names = users.map((u) => u.screen_name)
const api = setUsersApprovalStatus
const resultUsers = await api({
credentials: useCredentialsStore().current,
screen_names,
})
resultUsers.forEach((user) => {
window.vuex.commit('updateUserAdminData', { user })
})
},
reloadEmoji() {
return reloadEmoji({ credentials: useCredentialsStore().current })
},
importEmojiFromFS() {
return importEmojiFromFS({ credentials: useCredentialsStore().current })
},
- listEmojiPacks() {
- return listEmojiPacks({ credentials: useCredentialsStore().current })
+ listEmojiPacks(params) {
+ return listEmojiPacks({
+ ...params,
+ credentials: useCredentialsStore().current
+ })
},
- listRemoteEmojiPacks() {
+ listRemoteEmojiPacks(params) {
return listRemoteEmojiPacks({
+ ...params,
credentials: useCredentialsStore().current,
})
},
addNewEmojiFile({ packName, file, shortcode, filename }) {
return addNewEmojiFile({
packName,
file,
shortcode,
filename,
credentials: useCredentialsStore().current,
})
},
downloadRemoteEmojiPack({ instance, packName, as }) {
return downloadRemoteEmojiPack({
instance,
packName,
as,
credentials: useCredentialsStore().current,
})
},
downloadRemoteEmojiPackZIP({ url, packName }) {
return downloadRemoteEmojiPackZIP({
url,
packName,
credentials: useCredentialsStore().current,
})
},
createEmojiPack({ name }) {
return createEmojiPack({
name,
credentials: useCredentialsStore().current,
})
},
deleteEmojiPack({ name }) {
return createEmojiPack({
name,
credentials: useCredentialsStore().current,
})
},
saveEmojiPackMetadata({ name, newData }) {
return createEmojiPack({
name,
newData,
credentials: useCredentialsStore().current,
})
},
},
})
diff --git a/src/stores/emoji.js b/src/stores/emoji.js
index 83b897593e..2a30d651b8 100644
--- a/src/stores/emoji.js
+++ b/src/stores/emoji.js
@@ -1,331 +1,329 @@
import { merge } from 'lodash'
import { defineStore } from 'pinia'
import { useCredentialsStore } from 'src/stores/credentials.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { ensureFinalFallback } from 'src/i18n/languages.js'
import { listEmojiPacks } from 'src/services/api/api.service.js'
import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations'
const defaultState = {
// Custom emoji from server
customEmoji: [],
customEmojiFetched: false,
adminPacksLocal: null,
adminPacksLocalLoading: true,
// Unicode emoji from bundle
emoji: {},
emojiFetched: false,
unicodeEmojiAnnotations: {},
// Stickers
stickers: null,
}
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: {
setStickers(stickers) {
this.stickers = stickers
},
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 getAdminPacksLocal(refresh) {
if (!refresh && this.adminPacksLocal) return this.adminPacksLocal
this.adminPacksLocalLoading = true
this.adminPacksLocal = await this.getAdminPacks(
useInstanceStore().server,
- () =>
- listEmojiPacks({
+ (params) => listEmojiPacks({
+ ...params,
credentials: useCredentialsStore().current,
}),
)
this.adminPacksLocalLoading = false
},
async getAdminPacks(instance, listFunction) {
const currentUser = window.vuex.state.users.currentUser
if (!currentUser.rights.admin) return
const pageSize = 25
return await listFunction({
instance,
page: 1,
pageSize: 0,
})
- .then((data) => data.json())
.then((data) => {
if (data.error !== undefined) {
return Promise.reject(data.error)
}
const promises = []
for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
promises.push(
listFunction({
instance,
page: i,
pageSize,
})
- .then((data) => data.json())
.then((pageData) => {
if (pageData.error !== undefined) {
return Promise.reject(pageData.error)
}
return pageData.packs
}),
)
}
return Promise.all(promises).then((results) => {
return merge({}, ...results)
})
})
.then((allPacks) => {
// Sort by key
return Object.keys(allPacks)
.sort()
.reduce((acc, key) => {
if (key.length === 0) return acc
acc[key] = allPacks[key]
return acc
}, {})
})
.catch((data) => {
- this.displayError(data)
+ console.error(data)
})
},
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))
}
},
},
})

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 1, 1:28 AM (16 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802072
Default Alt Text
(36 KB)

Event Timeline