Allow copying emoji from posts

This commit is contained in:
Ekaterina Vaartis 2025-08-06 21:51:10 +03:00 committed by vaartis
commit 04c180e0d9
4 changed files with 189 additions and 3 deletions

View file

@ -2,7 +2,7 @@ import { unescape, flattenDeep } from 'lodash'
import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
import StillImage from 'src/components/still-image/still-image.vue'
import StillImageEmojiPopover from 'src/components/still-image/still-image-emoji-popover.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.js'
import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
@ -162,9 +162,10 @@ export default {
item,
this.emoji,
({ shortcode, url }) => {
return <StillImage
return <StillImageEmojiPopover
class="emoji img"
src={url}
shortcode={shortcode}
title={`:${shortcode}:`}
alt={`:${shortcode}:`}
/>

View file

@ -298,7 +298,7 @@ export default {
height: 32px;
}
.SelectComponent {
.Select {
display: inline-block;
}

View file

@ -0,0 +1,184 @@
<template>
<Popover
ref="emojiPopover"
trigger="click"
placement="top"
bound-to-selector=".status-container"
:bound-to="{ x: 'container' }"
:offset="{ y: 10 }"
@show="fetchEmojiPacksIfAdmin"
>
<template #trigger>
<StillImage v-bind="$attrs" />
</template>
<template #content>
<div class="emoji-popover">
<h3>{{ $attrs.title }}</h3>
<div class="emoji-popover-centered">
<StillImage
class="emoji"
v-bind="$attrs"
/>
</div>
<div v-if="isUserAdmin">
<button
class="button button-default btn emoji-popover-button"
type="button"
@click="copyToLocalPack"
:disabled="packName == ''"
>
{{ $t('admin_dash.emoji.copy_to_pack') }}
</button>
<SelectComponent
v-model="packName"
>
<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>
</div>
</div>
</template>
</Popover>
</template>
<script>
import { assign } from 'lodash'
import StillImage from "./still-image.vue"
import Popover from 'components/popover/popover.vue'
import SelectComponent from 'components/select/select.vue'
import { useInterfaceStore } from 'src/stores/interface'
export default {
components: { StillImage, Popover, SelectComponent },
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() {
console.log(this.packName)
this.$store.state.api.backendInteractor.addNewEmojiFile({
packName: this.packName,
file: this.$attrs.src,
shortcode: this.$attrs.shortcode,
filename: ""
}).then(resp => resp.json()).then(resp => {
if (resp.error !== undefined) {
this.displayError(resp.error)
return
}
this.$refs.emojiPopover.hidePopover()
this.packName = ''
})
},
// Copied from emoji_tab.js
loadPacksPaginated (listFunction) {
const pageSize = 25
const allPacks = {}
return listFunction({ instance: this.$store.state.instance.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: this.$store.state.instance.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 => {
this.knownLocalPacks = allPacks
for (const name of Object.keys(this.knownLocalPacks)) {
this.sortPackFiles(name)
}
})
},
sortPackFiles (nameOfPack) {
// Sort by key
const sorted = Object.keys(this.knownLocalPacks[nameOfPack].files).sort().reduce((acc, key) => {
if (key.length === 0) return acc
acc[key] = this.knownLocalPacks[nameOfPack].files[key]
return acc
}, {})
this.knownLocalPacks[nameOfPack].files = sorted
}
}
}
</script>
<style>
.emoji-popover {
margin: 0 0.5em 0.5em 0.5em;
text-align: center;
.emoji {
width: 64px;
height: 64px;
}
.emoji-popover-centered {
display: flex;
align-items: center;
justify-content: center;
}
.emoji-popover-button {
width: 100%;
margin-top: 0.5em;
}
.Select {
width: 100%;
}
}
</style>

View file

@ -1219,6 +1219,7 @@
"editing": "Editing {0}",
"copying": "Copying {0}",
"copy_to": "Copy to",
"copy_to_pack": "Copy to local pack",
"delete_title": "Delete?",
"metadata_changed": "Metadata different from saved",
"emoji_changed": "Unsaved emoji file changes, check highlighted emoji",