some shitty initial implementation of emoji picker with popover
This commit is contained in:
parent
518fcf856a
commit
296a6fa4e3
|
@ -115,12 +115,12 @@ const EmojiInput = {
|
||||||
caret: 0,
|
caret: 0,
|
||||||
focused: false,
|
focused: false,
|
||||||
blurTimeout: null,
|
blurTimeout: null,
|
||||||
showPicker: false,
|
|
||||||
temporarilyHideSuggestions: false,
|
temporarilyHideSuggestions: false,
|
||||||
keepOpen: false,
|
keepOpen: false,
|
||||||
disableClickOutside: false,
|
disableClickOutside: false,
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
overlayStyle: {}
|
overlayStyle: {},
|
||||||
|
pickerShown: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -142,7 +142,6 @@ const EmojiInput = {
|
||||||
return this.focused &&
|
return this.focused &&
|
||||||
this.suggestions &&
|
this.suggestions &&
|
||||||
this.suggestions.length > 0 &&
|
this.suggestions.length > 0 &&
|
||||||
!this.showPicker &&
|
|
||||||
!this.temporarilyHideSuggestions
|
!this.temporarilyHideSuggestions
|
||||||
},
|
},
|
||||||
textAtCaret () {
|
textAtCaret () {
|
||||||
|
@ -285,8 +284,8 @@ const EmojiInput = {
|
||||||
if (pickerInput) pickerInput.focus()
|
if (pickerInput) pickerInput.focus()
|
||||||
},
|
},
|
||||||
triggerShowPicker () {
|
triggerShowPicker () {
|
||||||
this.showPicker = true
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
this.$refs.picker.showPicker()
|
||||||
this.scrollIntoView()
|
this.scrollIntoView()
|
||||||
this.focusPickerInput()
|
this.focusPickerInput()
|
||||||
})
|
})
|
||||||
|
@ -299,12 +298,16 @@ const EmojiInput = {
|
||||||
}, 0)
|
}, 0)
|
||||||
},
|
},
|
||||||
togglePicker () {
|
togglePicker () {
|
||||||
|
console.log('piick')
|
||||||
this.input.focus()
|
this.input.focus()
|
||||||
this.showPicker = !this.showPicker
|
if (!this.pickerShown) {
|
||||||
if (this.showPicker) {
|
console.log('pick')
|
||||||
this.scrollIntoView()
|
this.scrollIntoView()
|
||||||
|
this.$refs.picker.showPicker()
|
||||||
this.$refs.picker.startEmojiLoad()
|
this.$refs.picker.startEmojiLoad()
|
||||||
this.$nextTick(this.focusPickerInput)
|
this.$nextTick(this.focusPickerInput)
|
||||||
|
} else {
|
||||||
|
this.$refs.picker.hidePicker()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
replace (replacement) {
|
replace (replacement) {
|
||||||
|
@ -441,6 +444,12 @@ const EmojiInput = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
onPickerShown () {
|
||||||
|
this.pickerShown = true
|
||||||
|
},
|
||||||
|
onPickerClosed () {
|
||||||
|
this.pickerShown = false
|
||||||
|
},
|
||||||
onBlur (e) {
|
onBlur (e) {
|
||||||
// Clicking on any suggestion removes focus from autocomplete,
|
// Clicking on any suggestion removes focus from autocomplete,
|
||||||
// preventing click handler ever executing.
|
// preventing click handler ever executing.
|
||||||
|
@ -458,9 +467,6 @@ const EmojiInput = {
|
||||||
this.blurTimeout = null
|
this.blurTimeout = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.keepOpen) {
|
|
||||||
this.showPicker = false
|
|
||||||
}
|
|
||||||
this.focused = true
|
this.focused = true
|
||||||
this.setCaret(e)
|
this.setCaret(e)
|
||||||
this.temporarilyHideSuggestions = false
|
this.temporarilyHideSuggestions = false
|
||||||
|
@ -523,27 +529,15 @@ const EmojiInput = {
|
||||||
this.input.focus()
|
this.input.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showPicker = false
|
|
||||||
},
|
},
|
||||||
onInput (e) {
|
onInput (e) {
|
||||||
this.showPicker = false
|
|
||||||
this.setCaret(e)
|
this.setCaret(e)
|
||||||
this.$emit('update:modelValue', e.target.value)
|
this.$emit('update:modelValue', e.target.value)
|
||||||
},
|
},
|
||||||
onClickInput (e) {
|
|
||||||
this.showPicker = false
|
|
||||||
},
|
|
||||||
onClickOutside (e) {
|
|
||||||
if (this.disableClickOutside) return
|
|
||||||
this.showPicker = false
|
|
||||||
},
|
|
||||||
onStickerUploaded (e) {
|
onStickerUploaded (e) {
|
||||||
this.showPicker = false
|
|
||||||
this.$emit('sticker-uploaded', e)
|
this.$emit('sticker-uploaded', e)
|
||||||
},
|
},
|
||||||
onStickerUploadFailed (e) {
|
onStickerUploadFailed (e) {
|
||||||
this.showPicker = false
|
|
||||||
this.$emit('sticker-upload-Failed', e)
|
this.$emit('sticker-upload-Failed', e)
|
||||||
},
|
},
|
||||||
setCaret ({ target: { selectionStart } }) {
|
setCaret ({ target: { selectionStart } }) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="root"
|
ref="root"
|
||||||
v-click-outside="onClickOutside"
|
|
||||||
class="emoji-input"
|
class="emoji-input"
|
||||||
:class="{ 'with-picker': !hideEmojiButton }"
|
:class="{ 'with-picker': !hideEmojiButton }"
|
||||||
>
|
>
|
||||||
|
@ -24,13 +23,13 @@
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
v-if="enableEmojiPicker"
|
v-if="enableEmojiPicker"
|
||||||
ref="picker"
|
ref="picker"
|
||||||
:class="{ hide: !showPicker }"
|
|
||||||
:showing="showPicker"
|
|
||||||
:enable-sticker-picker="enableStickerPicker"
|
:enable-sticker-picker="enableStickerPicker"
|
||||||
class="emoji-picker-panel"
|
class="emoji-picker-panel"
|
||||||
@emoji="insert"
|
@emoji="insert"
|
||||||
@sticker-uploaded="onStickerUploaded"
|
@sticker-uploaded="onStickerUploaded"
|
||||||
@sticker-upload-failed="onStickerUploadFailed"
|
@sticker-upload-failed="onStickerUploadFailed"
|
||||||
|
@show="onPickerShown"
|
||||||
|
@close="onPickerClosed"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<Popover
|
<Popover
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
import Checkbox from '../checkbox/checkbox.vue'
|
||||||
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
import StillImage from '../still-image/still-image.vue'
|
import StillImage from '../still-image/still-image.vue'
|
||||||
import { ensureFinalFallback } from '../../i18n/languages.js'
|
import { ensureFinalFallback } from '../../i18n/languages.js'
|
||||||
import lozad from 'lozad'
|
import lozad from 'lozad'
|
||||||
|
@ -87,10 +88,6 @@ const EmojiPicker = {
|
||||||
required: false,
|
required: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
|
||||||
showing: {
|
|
||||||
required: true,
|
|
||||||
type: Boolean
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
@ -111,15 +108,30 @@ const EmojiPicker = {
|
||||||
components: {
|
components: {
|
||||||
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
|
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
|
||||||
Checkbox,
|
Checkbox,
|
||||||
StillImage
|
StillImage,
|
||||||
|
Popover
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
showPicker () {
|
||||||
|
console.log('pick')
|
||||||
|
this.$refs.popover.showPopover()
|
||||||
|
this.onShowing()
|
||||||
|
},
|
||||||
|
hidePicker () {
|
||||||
|
this.$refs.popover.hidePopover()
|
||||||
|
},
|
||||||
setGroupRef (name) {
|
setGroupRef (name) {
|
||||||
return el => { this.groupRefs[name] = el }
|
return el => { this.groupRefs[name] = el }
|
||||||
},
|
},
|
||||||
setEmojiRef (name) {
|
setEmojiRef (name) {
|
||||||
return el => { this.emojiRefs[name] = el }
|
return el => { this.emojiRefs[name] = el }
|
||||||
},
|
},
|
||||||
|
onPopoverShown () {
|
||||||
|
this.$emit('show')
|
||||||
|
},
|
||||||
|
onPopoverClosed () {
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
onStickerUploaded (e) {
|
onStickerUploaded (e) {
|
||||||
this.$emit('sticker-uploaded', e)
|
this.$emit('sticker-uploaded', e)
|
||||||
},
|
},
|
||||||
|
@ -251,16 +263,6 @@ const EmojiPicker = {
|
||||||
allCustomGroups () {
|
allCustomGroups () {
|
||||||
this.waitForDomAndInitializeLazyLoad()
|
this.waitForDomAndInitializeLazyLoad()
|
||||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||||
},
|
|
||||||
showing (val) {
|
|
||||||
if (val) {
|
|
||||||
this.onShowing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
if (this.showing) {
|
|
||||||
this.onShowing()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
|
|
|
@ -6,14 +6,10 @@ $emoji-picker-header-picture-height: 32px;
|
||||||
$emoji-picker-emoji-size: 32px;
|
$emoji-picker-emoji-size: 32px;
|
||||||
|
|
||||||
.emoji-picker {
|
.emoji-picker {
|
||||||
|
width: 25em;
|
||||||
|
max-width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
margin: 0 !important;
|
|
||||||
// TODO: actually use popover in emoji picker
|
|
||||||
z-index: var(--ZI_popovers);
|
|
||||||
background-color: $fallback--bg;
|
background-color: $fallback--bg;
|
||||||
background-color: var(--popover, $fallback--bg);
|
background-color: var(--popover, $fallback--bg);
|
||||||
color: $fallback--link;
|
color: $fallback--link;
|
||||||
|
|
|
@ -1,129 +1,135 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<Popover
|
||||||
class="emoji-picker panel panel-default panel-body"
|
trigger="click"
|
||||||
|
popover-class="emoji-picker popover-default"
|
||||||
|
ref="popover"
|
||||||
|
@show="onPopoverShown"
|
||||||
|
@close="onPopoverClosed"
|
||||||
>
|
>
|
||||||
<div class="heading">
|
<template #content>
|
||||||
<span
|
<div class="heading">
|
||||||
ref="header"
|
|
||||||
class="emoji-tabs"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
v-for="group in filteredEmojiGroups"
|
ref="header"
|
||||||
:ref="setGroupRef('group-header-' + group.id)"
|
class="emoji-tabs"
|
||||||
:key="group.id"
|
|
||||||
class="emoji-tabs-item"
|
|
||||||
:class="{
|
|
||||||
active: activeGroupView === group.id
|
|
||||||
}"
|
|
||||||
:title="group.text"
|
|
||||||
@click.prevent="highlight(group.id)"
|
|
||||||
>
|
>
|
||||||
<span
|
<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"
|
v-for="group in filteredEmojiGroups"
|
||||||
|
:ref="setGroupRef('group-header-' + group.id)"
|
||||||
:key="group.id"
|
:key="group.id"
|
||||||
class="emoji-group"
|
class="emoji-tabs-item"
|
||||||
|
:class="{
|
||||||
|
active: activeGroupView === group.id
|
||||||
|
}"
|
||||||
|
:title="group.text"
|
||||||
|
@click.prevent="highlight(group.id)"
|
||||||
>
|
>
|
||||||
<h6
|
|
||||||
:ref="setGroupRef('group-' + group.id)"
|
|
||||||
class="emoji-group-title"
|
|
||||||
>
|
|
||||||
{{ group.text }}
|
|
||||||
</h6>
|
|
||||||
<span
|
<span
|
||||||
v-for="emoji in group.emojis"
|
v-if="group.image"
|
||||||
:key="group.id + emoji.displayText"
|
class="emoji-picker-header-image"
|
||||||
:title="maybeLocalizedEmojiName(emoji)"
|
|
||||||
class="emoji-item"
|
|
||||||
@click.stop.prevent="onEmoji(emoji)"
|
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
v-if="!emoji.imageUrl"
|
|
||||||
class="emoji-picker-emoji -unicode"
|
|
||||||
>{{ emoji.replacement }}</span>
|
|
||||||
<still-image
|
<still-image
|
||||||
v-else
|
:alt="group.text"
|
||||||
:ref="setEmojiRef(group.id + emoji.displayText)"
|
:src="group.image"
|
||||||
class="emoji-picker-emoji -custom"
|
|
||||||
:data-src="emoji.imageUrl"
|
|
||||||
:data-emoji-name="group.id + emoji.displayText"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span :ref="setGroupRef('group-end-' + group.id)" />
|
<FAIcon
|
||||||
</div>
|
v-else
|
||||||
</div>
|
:icon="group.icon"
|
||||||
<div class="keep-open">
|
fixed-width
|
||||||
<Checkbox v-model="keepOpen">
|
/>
|
||||||
{{ $t('emoji.keep_open') }}
|
</span>
|
||||||
</Checkbox>
|
</span>
|
||||||
</div>
|
<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>
|
||||||
<div
|
<div
|
||||||
v-if="showingStickers"
|
v-if="contentLoaded"
|
||||||
class="stickers-content"
|
class="content"
|
||||||
>
|
>
|
||||||
<sticker-picker
|
<div
|
||||||
@uploaded="onStickerUploaded"
|
class="emoji-content"
|
||||||
@upload-failed="onStickerUploadFailed"
|
: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="setGroupRef('group-' + group.id)"
|
||||||
|
class="emoji-group-title"
|
||||||
|
>
|
||||||
|
{{ group.text }}
|
||||||
|
</h6>
|
||||||
|
<span
|
||||||
|
v-for="emoji in group.emojis"
|
||||||
|
:key="group.id + emoji.displayText"
|
||||||
|
:title="maybeLocalizedEmojiName(emoji)"
|
||||||
|
class="emoji-item"
|
||||||
|
@click.stop.prevent="onEmoji(emoji)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="!emoji.imageUrl"
|
||||||
|
class="emoji-picker-emoji -unicode"
|
||||||
|
>{{ emoji.replacement }}</span>
|
||||||
|
<still-image
|
||||||
|
v-else
|
||||||
|
:ref="setEmojiRef(group.id + emoji.displayText)"
|
||||||
|
class="emoji-picker-emoji -custom"
|
||||||
|
:data-src="emoji.imageUrl"
|
||||||
|
:data-emoji-name="group.id + emoji.displayText"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span :ref="setGroupRef('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>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./emoji_picker.js"></script>
|
<script src="./emoji_picker.js"></script>
|
||||||
|
|
|
@ -63,6 +63,7 @@ const Popover = {
|
||||||
// used to avoid blinking if hovered onto popover
|
// used to avoid blinking if hovered onto popover
|
||||||
graceTimeout: null,
|
graceTimeout: null,
|
||||||
parentPopover: null,
|
parentPopover: null,
|
||||||
|
disableClickOutside: false,
|
||||||
childrenShown: new Set()
|
childrenShown: new Set()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -234,6 +235,10 @@ const Popover = {
|
||||||
},
|
},
|
||||||
showPopover () {
|
showPopover () {
|
||||||
if (this.disabled) return
|
if (this.disabled) return
|
||||||
|
this.disableClickOutside = true
|
||||||
|
setTimeout(() => {
|
||||||
|
this.disableClickOutside = false
|
||||||
|
}, 0)
|
||||||
const wasHidden = this.hidden
|
const wasHidden = this.hidden
|
||||||
this.hidden = false
|
this.hidden = false
|
||||||
this.parentPopover && this.parentPopover.onChildPopoverState(this, true)
|
this.parentPopover && this.parentPopover.onChildPopoverState(this, true)
|
||||||
|
@ -294,6 +299,7 @@ const Popover = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClickOutside (e) {
|
onClickOutside (e) {
|
||||||
|
if (this.disableClickOutside) return
|
||||||
if (this.hidden) return
|
if (this.hidden) return
|
||||||
if (this.$refs.content && this.$refs.content.contains(e.target)) return
|
if (this.$refs.content && this.$refs.content.contains(e.target)) return
|
||||||
if (this.$el.contains(e.target)) return
|
if (this.$el.contains(e.target)) return
|
||||||
|
|
Loading…
Reference in New Issue