Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
13 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index d04649dcca..cab952f8ef 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -1,256 +1,301 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
faStickyNote,
faSmileBeam
} from '@fortawesome/free-solid-svg-icons'
import { trim } from 'lodash'
library.add(
faBoxOpen,
faStickyNote,
faSmileBeam
)
// At widest, approximately 20 emoji are visible in a row,
// loading 3 rows, could be overkill for narrow picker
const LOAD_EMOJI_BY = 60
// When to start loading new batch emoji, in pixels
const LOAD_EMOJI_MARGIN = 64
const filterByKeyword = (list, keyword = '') => {
if (keyword === '') return list
const keywordLowercase = keyword.toLowerCase()
const orderedEmojiList = []
for (const emoji of list) {
const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase)
if (indexOfKeyword > -1) {
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
orderedEmojiList[indexOfKeyword] = []
}
orderedEmojiList[indexOfKeyword].push(emoji)
}
}
return orderedEmojiList.flat()
}
const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5)
const EmojiPicker = {
props: {
enableStickerPicker: {
required: false,
type: Boolean,
default: false
}
},
data () {
return {
keyword: '',
activeGroup: 'custom',
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
customEmojiBufferSlice: LOAD_EMOJI_BY,
customEmojiTimeout: null,
- customEmojiLoadAllConfirmed: false
+ customEmojiLoadAllConfirmed: false,
+ groupLoadedCount: {}
}
},
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox
},
methods: {
onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
onStickerUploadFailed (e) {
this.$emit('sticker-upload-failed', e)
},
onEmoji (emoji) {
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
},
onScroll (e) {
const target = (e && e.target) || this.$refs['emoji-groups']
this.updateScrolledClass(target)
this.scrolledGroup(target)
- this.triggerLoadMore(target)
+ this.$nextTick(() => {
+ this.triggerLoadMore(target)
+ })
},
highlight (key) {
const ref = this.$refs['group-' + key]
const top = ref.offsetTop
this.setShowStickers(false)
this.activeGroup = key
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = top + 1
+ this.loadEmoji(key)
})
},
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
this.groupsScrolledClass = 'scrolled-top'
} else if (target.scrollTop >= target.scrollTopMax - 5) {
this.groupsScrolledClass = 'scrolled-bottom'
} else {
this.groupsScrolledClass = 'scrolled-middle'
}
},
triggerLoadMore (target) {
- const ref = this.$refs[`group-end-${this.lastNonUnicodeGroupId}`][0]
- if (!ref) return
- const bottom = ref.offsetTop + ref.offsetHeight
+ Object.keys(this.allCustomGroups)
+ .map(groupId => {
+ const ref = this.$refs[`group-end-${groupId}`][0]
+ if (!ref) return undefined
- const scrollerBottom = target.scrollTop + target.clientHeight
- const scrollerTop = target.scrollTop
- const scrollerMax = target.scrollHeight
+ const bottom = ref.offsetTop + ref.offsetHeight
- // Loads more emoji when they come into view
- const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
- // Always load when at the very top in case there's no scroll space yet
- const atTop = scrollerTop < 5
- // Don't load when looking at unicode category or at the very bottom
- const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
- if (!bottomAboveViewport && (approachingBottom || atTop)) {
- this.loadEmoji()
- }
+ const group = this.$refs[`group-${groupId}`][0]
+ const top = group.offsetTop
+
+ const scrollerBottom = target.scrollTop + target.clientHeight
+ const scrollerTop = target.scrollTop
+ const scrollerMax = target.scrollHeight
+
+ // Loads more emoji when they come into view
+ const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
+ // Always load when at the very top in case there's no scroll space yet
+ const atTop = scrollerTop < top + target.clientHeight / 2 && top < scrollerBottom
+ // Don't load when looking at unicode category or at the very bottom
+ const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
+ if (!bottomAboveViewport && (approachingBottom || atTop)) {
+ return groupId
+ }
+ return undefined
+ })
+ .filter(k => k)
+ .map(k => {
+ this.loadEmoji(k)
+ })
},
scrolledGroup (target) {
const top = target.scrollTop + 5
this.$nextTick(() => {
- this.emojisView.forEach(group => {
+ this.allEmojiGroups.forEach(group => {
const ref = this.$refs['group-' + group.id]
if (ref.offsetTop <= top) {
this.activeGroup = group.id
}
})
})
},
- loadEmoji () {
- const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
+ loadEmoji (loadGroup) {
+ if (!this.allCustomGroups[loadGroup]) {
+ return
+ }
+
+ const allLoaded = this.loadedCount[loadGroup] >= this.allCustomGroups[loadGroup].emojis.length
if (allLoaded) {
return
}
- this.customEmojiBufferSlice += LOAD_EMOJI_BY
+ this.groupLoadedCount = {
+ ...this.groupLoadedCount,
+ [loadGroup]: this.loadedCount[loadGroup] + LOAD_EMOJI_BY
+ }
},
startEmojiLoad (forceUpdate = false) {
if (!forceUpdate) {
this.keyword = ''
}
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = 0
})
const bufferSize = this.customEmojiBuffer.length
const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
if (bufferPrefilledAll && !forceUpdate) {
return
}
this.customEmojiBufferSlice = LOAD_EMOJI_BY
},
toggleStickers () {
this.showingStickers = !this.showingStickers
},
setShowStickers (value) {
this.showingStickers = value
+ },
+ limitedEmojis (list, groupId) {
+ return list.slice(0, this.loadedCount[groupId])
}
},
watch: {
keyword () {
this.customEmojiLoadAllConfirmed = false
this.onScroll()
this.startEmojiLoad(true)
}
},
computed: {
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
},
stickersAvailable () {
if (this.$store.state.instance.stickers) {
return this.$store.state.instance.stickers.length > 0
}
return 0
},
allEmojis () {
return this.$store.state.instance.customEmoji || []
},
filteredEmoji () {
return filterByKeyword(
this.allEmojis,
trim(this.keyword)
)
},
customEmojiBuffer () {
return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
},
groupedCustomEmojis () {
return this.customEmojiBuffer.reduce((res, emoji) => {
const pack = packOf(emoji)
if (!res[pack]) {
res[pack] = {
id: `custom-${pack}`,
text: pack,
/// FIXME
// icon: 'smile-beam',
image: emoji.imageUrl,
emojis: []
}
}
res[pack].emojis.push(emoji)
return res
}, {})
},
- allEmojiGroups () {
- return this.allEmojis
+ allCustomGroups () {
+ return this.filteredEmoji
.reduce((res, emoji) => {
const packName = packOf(emoji)
const packId = `custom-${packName}`
- if (res.filter(k => k.id === packId).length === 0) {
- res.push({
+ if (!res[packId]) {
+ res[packId] = ({
id: packId,
text: packName,
- image: emoji.imageUrl
+ image: emoji.imageUrl,
+ emojis: []
})
}
+ res[packId].emojis.push(emoji)
return res
- }, [])
+ }, {})
+ },
+ sensibleInitialAmountForAGroup () {
+ const groupCount = Object.keys(this.allCustomGroups).length
+ return Math.max(Math.floor(LOAD_EMOJI_BY / Math.max(groupCount, 1)), 1)
+ },
+ allEmojiGroups () {
+ const standardEmojis = this.$store.state.instance.emoji || []
+ return Object.entries(this.allCustomGroups)
+ .map(([_, v]) => v)
.concat({
id: 'standard',
text: this.$t('emoji.unicode'),
- icon: 'box-open'
+ icon: 'box-open',
+ emojis: filterByKeyword(standardEmojis, this.keyword)
})
},
emojis () {
const standardEmojis = this.$store.state.instance.emoji || []
// const customEmojis = this.customEmojiBuffer
return [
...Object
.keys(this.groupedCustomEmojis)
.map(k => this.groupedCustomEmojis[k]),
{
id: 'standard',
text: this.$t('emoji.unicode'),
icon: 'box-open',
emojis: filterByKeyword(standardEmojis, trim(this.keyword))
}
]
},
+ loadedCount () {
+ return Object.keys(this.allCustomGroups)
+ .reduce((res, groupId) => {
+ res[groupId] = this.groupLoadedCount[groupId] || this.sensibleInitialAmountForAGroup
+ return res
+ }, {})
+ },
lastNonUnicodeGroupId () {
return this.emojis[this.emojis.length - 2].id
},
emojisView () {
return this.emojis.filter(value => value.emojis.length > 0)
},
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0
}
}
}
export default EmojiPicker
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index fe60cb5ef6..277d5bf625 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,116 +1,116 @@
<template>
<div class="emoji-picker panel panel-default panel-body">
<div class="heading">
<span class="emoji-tabs">
<span
v-for="group in allEmojiGroups"
:key="group.id"
class="emoji-tabs-item"
:class="{
active: activeGroupView === group.id,
disabled: false
}"
:title="group.text"
@click.prevent="highlight(group.id)"
>
<span
v-if="group.image"
class="emoji-picker-header-image"
>
<img
:alt="group.text"
:src="group.image"
>
</span>
<FAIcon
v-else
:icon="group.icon"
fixed-width
/>
</span>
</span>
<span
v-if="stickerPickerEnabled"
class="additional-tabs"
>
<span
class="stickers-tab-icon additional-tabs-item"
:class="{active: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
>
<FAIcon
icon="sticky-note"
fixed-width
/>
</span>
</span>
</div>
<div class="content">
<div
class="emoji-content"
:class="{hidden: showingStickers}"
>
<div class="emoji-search">
<input
v-model="keyword"
type="text"
class="form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
<div
ref="emoji-groups"
class="emoji-groups"
:class="groupsScrolledClass"
@scroll="onScroll"
>
<div
- v-for="group in emojisView"
+ v-for="group in allEmojiGroups"
:key="group.id"
class="emoji-group"
>
<h6
:ref="'group-' + group.id"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span
- v-for="emoji in group.emojis"
+ v-for="emoji in limitedEmojis(group.emojis, group.id)"
:key="group.id + emoji.displayText"
:title="emoji.displayText"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
<span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
<img
v-else
:src="emoji.imageUrl"
>
</span>
<span :ref="'group-end-' + group.id" />
</div>
</div>
<div class="keep-open">
<Checkbox v-model="keepOpen">
{{ $t('emoji.keep_open') }}
</Checkbox>
</div>
</div>
<div
v-if="showingStickers"
class="stickers-content"
>
<sticker-picker
@uploaded="onStickerUploaded"
@upload-failed="onStickerUploadFailed"
/>
</div>
</div>
</div>
</template>
<script src="./emoji_picker.js"></script>
<style lang="scss" src="./emoji_picker.scss"></style>

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 2, 8:42 AM (5 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1803945
Default Alt Text
(13 KB)

Event Timeline