Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85828635
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
13 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index ea53a972a4..315364d5bb 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -1,213 +1,224 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
import StillImage from '../still-image/still-image.vue'
import lozad from 'lozad'
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
)
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 EmojiPicker = {
props: {
enableStickerPicker: {
required: false,
type: Boolean,
default: false
},
showing: {
required: true,
type: Boolean
}
},
data () {
return {
keyword: '',
activeGroup: 'custom',
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
customEmojiTimeout: null,
// Lazy-load only after the first time `showing` becomes true.
contentLoaded: false
}
},
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox,
StillImage
},
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)
},
scrolledGroup (target) {
const top = target.scrollTop + 5
this.$nextTick(() => {
this.allEmojiGroups.forEach(group => {
const ref = this.$refs['group-' + group.id]
if (ref[0].offsetTop <= top) {
this.activeGroup = group.id
}
})
})
},
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
})
},
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'
}
},
toggleStickers () {
this.showingStickers = !this.showingStickers
},
setShowStickers (value) {
this.showingStickers = value
},
filterByKeyword (list, keyword) {
return filterByKeyword(list, keyword)
},
initializeLazyLoad () {
this.destroyLazyLoad()
- this.$lozad = lozad('img', {})
- this.$lozad.observe()
+ this.$nextTick(() => {
+ this.$lozad = lozad('.still-image.emoji-picker-emoji', {
+ load: el => {
+ const vn = el.__vue__
+ if (!vn) {
+ return
+ }
+
+ vn.loadLazy()
+ }
+ })
+ this.$lozad.observe()
+ })
},
waitForDomAndInitializeLazyLoad () {
this.$nextTick(() => this.initializeLazyLoad())
},
destroyLazyLoad () {
if (this.$lozad) {
if (this.$lozad.observer) {
this.$lozad.observer.disconnect()
}
if (this.$lozad.mutationObserver) {
this.$lozad.mutationObserver.disconnect()
}
}
},
onShowing () {
const oldContentLoaded = this.contentLoaded
this.contentLoaded = true
this.waitForDomAndInitializeLazyLoad()
if (!oldContentLoaded) {
this.$nextTick(() => {
if (this.defaultGroup) {
this.highlight(this.defaultGroup)
}
})
}
}
},
watch: {
keyword () {
this.onScroll()
this.waitForDomAndInitializeLazyLoad()
},
allCustomGroups () {
this.waitForDomAndInitializeLazyLoad()
},
showing (val) {
if (val) {
this.onShowing()
}
}
},
mounted () {
if (this.showing) {
this.onShowing()
}
},
destroyed () {
this.destroyLazyLoad()
},
computed: {
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
},
stickersAvailable () {
if (this.$store.state.instance.stickers) {
return this.$store.state.instance.stickers.length > 0
}
return 0
},
allCustomGroups () {
return this.$store.getters.groupedCustomEmojis
},
defaultGroup () {
return Object.keys(this.allCustomGroups)[0]
},
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',
emojis: filterByKeyword(standardEmojis, this.keyword)
})
},
filteredEmojiGroups () {
return this.allEmojiGroups
.map(group => ({
...group,
emojis: filterByKeyword(group.emojis, this.keyword)
}))
.filter(group => group.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 b92bccd74f..19cc46b541 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,120 +1,121 @@
<template>
<div
class="emoji-picker panel panel-default panel-body"
>
<div class="heading">
<span class="emoji-tabs">
<span
v-for="group in filteredEmojiGroups"
:key="group.id"
class="emoji-tabs-item"
:class="{
active: activeGroupView === group.id
}"
:title="group.text"
@click.prevent="highlight(group.id)"
>
<span
v-if="group.image"
class="emoji-picker-header-image"
>
<still-image
: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
v-if="contentLoaded"
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 filteredEmojiGroups"
: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"
: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
+ <still-image
v-else
+ class="emoji-picker-emoji"
:data-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>
diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js
index d7abbcb5ee..1806d33b01 100644
--- a/src/components/still-image/still-image.js
+++ b/src/components/still-image/still-image.js
@@ -1,48 +1,63 @@
const StillImage = {
props: [
'src',
'referrerpolicy',
'mimetype',
'imageLoadError',
'imageLoadHandler',
'alt',
'height',
- 'width'
+ 'width',
+ 'dataSrc'
],
data () {
return {
+ // for lazy loading, see loadLazy()
+ realSrc: this.src,
stopGifs: this.$store.getters.mergedConfig.stopGifs
}
},
computed: {
animated () {
- return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif'))
+ if (!this.realSrc) {
+ return false
+ }
+
+ return this.stopGifs && (this.mimetype === 'image/gif' || this.realSrc.endsWith('.gif'))
},
style () {
const appendPx = (str) => /\d$/.test(str) ? str + 'px' : str
return {
height: this.height ? appendPx(this.height) : null,
width: this.width ? appendPx(this.width) : null
}
}
},
methods: {
+ loadLazy () {
+ if (this.dataSrc) {
+ this.realSrc = this.dataSrc
+ }
+ },
onLoad () {
+ if (!this.realSrc) {
+ return
+ }
const image = this.$refs.src
if (!image) return
this.imageLoadHandler && this.imageLoadHandler(image)
const canvas = this.$refs.canvas
if (!canvas) return
const width = image.naturalWidth
const height = image.naturalHeight
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
},
onError () {
this.imageLoadError && this.imageLoadError()
}
}
}
export default StillImage
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index ab3080c8c1..633fb229b6 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -1,92 +1,93 @@
<template>
<div
class="still-image"
:class="{ animated: animated }"
:style="style"
>
<canvas
v-if="animated"
ref="canvas"
/>
<!-- NOTE: key is required to force to re-render img tag when src is changed -->
<img
ref="src"
- :key="src"
+ :key="realSrc"
:alt="alt"
:title="alt"
- :src="src"
+ :data-src="dataSrc"
+ :src="realSrc"
:referrerpolicy="referrerpolicy"
@load="onLoad"
@error="onError"
>
<slot />
</div>
</template>
<script src="./still-image.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.still-image {
position: relative;
line-height: 0;
overflow: hidden;
display: inline-flex;
align-items: center;
canvas {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
object-fit: contain;
visibility: var(--_still-image-canvas-visibility, visible);
}
img {
width: 100%;
height: 100%;
object-fit: contain;
}
&.animated {
&::before {
zoom: var(--_still_image-label-scale, 1);
content: 'gif';
position: absolute;
line-height: 1;
font-size: 0.7em;
top: 0.5em;
left: 0.5em;
background: rgba(127, 127, 127, 0.5);
color: #fff;
display: block;
padding: 2px 4px;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
z-index: 2;
visibility: var(--_still-image-label-visibility, visible);
}
&:hover canvas {
display: none;
}
&:hover::before {
visibility: var(--_still-image-label-visibility, hidden);
}
img {
visibility: var(--_still-image-img-visibility, hidden);
}
&:hover img {
visibility: visible;
}
}
}
</style>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 31, 10:05 AM (20 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1801245
Default Alt Text
(13 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline