Merge branch 'emoji-popovers' into shigusegubu-vue3
* emoji-popovers: use anchor for picker move keepOpen to picker some shitty initial implementation of emoji picker with popover fix blinking popup fix css cleanup moved popovers space outside app because otherwise it causes weird issues A LOT suggestor popover
This commit is contained in:
commit
3d7f5f7be8
10 changed files with 342 additions and 323 deletions
|
@ -15,5 +15,6 @@
|
||||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
|
<div id="popovers" />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -73,7 +73,6 @@
|
||||||
<UpdateNotification />
|
<UpdateNotification />
|
||||||
<div id="modal" />
|
<div id="modal" />
|
||||||
<GlobalNoticeList />
|
<GlobalNoticeList />
|
||||||
<div id="popovers" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Completion from '../../services/completion/completion.js'
|
import Completion from '../../services/completion/completion.js'
|
||||||
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
||||||
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
||||||
import { take } from 'lodash'
|
import { take } from 'lodash'
|
||||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||||
|
@ -109,18 +110,20 @@ const EmojiInput = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
input: undefined,
|
input: undefined,
|
||||||
|
caretEl: undefined,
|
||||||
highlighted: 0,
|
highlighted: 0,
|
||||||
caret: 0,
|
caret: 0,
|
||||||
focused: false,
|
focused: false,
|
||||||
blurTimeout: null,
|
blurTimeout: null,
|
||||||
showPicker: false,
|
|
||||||
temporarilyHideSuggestions: false,
|
temporarilyHideSuggestions: false,
|
||||||
keepOpen: false,
|
|
||||||
disableClickOutside: false,
|
disableClickOutside: false,
|
||||||
suggestions: []
|
suggestions: [],
|
||||||
|
overlayStyle: {},
|
||||||
|
pickerShown: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
Popover,
|
||||||
EmojiPicker,
|
EmojiPicker,
|
||||||
UnicodeDomainIndicator
|
UnicodeDomainIndicator
|
||||||
},
|
},
|
||||||
|
@ -128,11 +131,17 @@ const EmojiInput = {
|
||||||
padEmoji () {
|
padEmoji () {
|
||||||
return this.$store.getters.mergedConfig.padEmoji
|
return this.$store.getters.mergedConfig.padEmoji
|
||||||
},
|
},
|
||||||
|
preText () {
|
||||||
|
return this.modelValue.slice(0, this.caret)
|
||||||
|
},
|
||||||
|
postText () {
|
||||||
|
return this.modelValue.slice(this.caret)
|
||||||
|
},
|
||||||
showSuggestions () {
|
showSuggestions () {
|
||||||
return this.focused &&
|
return this.focused &&
|
||||||
this.suggestions &&
|
this.suggestions &&
|
||||||
this.suggestions.length > 0 &&
|
this.suggestions.length > 0 &&
|
||||||
!this.showPicker &&
|
!this.pickerShown &&
|
||||||
!this.temporarilyHideSuggestions
|
!this.temporarilyHideSuggestions
|
||||||
},
|
},
|
||||||
textAtCaret () {
|
textAtCaret () {
|
||||||
|
@ -191,10 +200,22 @@ const EmojiInput = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
const { root } = this.$refs
|
const { root, hiddenOverlayCaret, suggestorPopover } = this.$refs
|
||||||
const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
|
const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
|
||||||
if (!input) return
|
if (!input) return
|
||||||
this.input = input
|
this.input = input
|
||||||
|
this.caretEl = hiddenOverlayCaret
|
||||||
|
suggestorPopover.setAnchorEl(this.caretEl)
|
||||||
|
this.$refs.picker.setAnchorEl(this.caretEl)
|
||||||
|
const style = getComputedStyle(this.input)
|
||||||
|
this.overlayStyle.padding = style.padding
|
||||||
|
this.overlayStyle.border = style.border
|
||||||
|
this.overlayStyle.margin = style.margin
|
||||||
|
this.overlayStyle.lineHeight = style.lineHeight
|
||||||
|
this.overlayStyle.fontFamily = style.fontFamily
|
||||||
|
this.overlayStyle.fontSize = style.fontSize
|
||||||
|
this.overlayStyle.wordWrap = style.wordWrap
|
||||||
|
this.overlayStyle.whiteSpace = style.whiteSpace
|
||||||
this.resize()
|
this.resize()
|
||||||
input.addEventListener('blur', this.onBlur)
|
input.addEventListener('blur', this.onBlur)
|
||||||
input.addEventListener('focus', this.onFocus)
|
input.addEventListener('focus', this.onFocus)
|
||||||
|
@ -204,6 +225,13 @@ const EmojiInput = {
|
||||||
input.addEventListener('click', this.onClickInput)
|
input.addEventListener('click', this.onClickInput)
|
||||||
input.addEventListener('transitionend', this.onTransition)
|
input.addEventListener('transitionend', this.onTransition)
|
||||||
input.addEventListener('input', this.onInput)
|
input.addEventListener('input', this.onInput)
|
||||||
|
// FIXME LEAK
|
||||||
|
input.addEventListener('scroll', (e) => {
|
||||||
|
this.$refs.hiddenOverlay.scrollTo({
|
||||||
|
top: this.input.scrollTop,
|
||||||
|
left: this.input.scrollLeft
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
unmounted () {
|
unmounted () {
|
||||||
const { input } = this
|
const { input } = this
|
||||||
|
@ -219,28 +247,34 @@ const EmojiInput = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
showSuggestions: function (newValue) {
|
showSuggestions: function (newValue, oldValue) {
|
||||||
this.$emit('shown', newValue)
|
this.$emit('shown', newValue)
|
||||||
|
if (newValue) {
|
||||||
|
this.$refs.suggestorPopover.showPopover()
|
||||||
|
} else {
|
||||||
|
this.$refs.suggestorPopover.hidePopover()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
textAtCaret: async function (newWord) {
|
textAtCaret: async function (newWord) {
|
||||||
const firstchar = newWord.charAt(0)
|
const firstchar = newWord.charAt(0)
|
||||||
this.suggestions = []
|
if (newWord === firstchar) {
|
||||||
if (newWord === firstchar) return
|
if (firstchar === ' ') {
|
||||||
|
this.suggestions = []
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
|
const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
|
||||||
// Async: cancel if textAtCaret has changed during wait
|
// Async: cancel if textAtCaret has changed during wait
|
||||||
if (this.textAtCaret !== newWord) return
|
if (this.textAtCaret !== newWord || matchedSuggestions.length <= 0) {
|
||||||
if (matchedSuggestions.length <= 0) return
|
this.suggestions = []
|
||||||
|
return
|
||||||
|
}
|
||||||
this.suggestions = take(matchedSuggestions, 5)
|
this.suggestions = take(matchedSuggestions, 5)
|
||||||
.map(({ imageUrl, ...rest }) => ({
|
.map(({ imageUrl, ...rest }) => ({
|
||||||
...rest,
|
...rest,
|
||||||
img: imageUrl || ''
|
img: imageUrl || ''
|
||||||
}))
|
}))
|
||||||
},
|
this.$refs.suggestorPopover.updateStyles()
|
||||||
suggestions: {
|
|
||||||
handler (newValue) {
|
|
||||||
this.$nextTick(this.resize)
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -251,8 +285,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()
|
||||||
})
|
})
|
||||||
|
@ -265,12 +299,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) {
|
||||||
|
@ -307,7 +345,6 @@ const EmojiInput = {
|
||||||
spaceAfter,
|
spaceAfter,
|
||||||
after
|
after
|
||||||
].join('')
|
].join('')
|
||||||
this.keepOpen = keepOpen
|
|
||||||
this.$emit('update:modelValue', newValue)
|
this.$emit('update:modelValue', newValue)
|
||||||
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
|
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
|
||||||
if (!keepOpen) {
|
if (!keepOpen) {
|
||||||
|
@ -407,8 +444,11 @@ const EmojiInput = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onTransition (e) {
|
onPickerShown () {
|
||||||
this.resize()
|
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,
|
||||||
|
@ -416,7 +456,6 @@ const EmojiInput = {
|
||||||
this.blurTimeout = setTimeout(() => {
|
this.blurTimeout = setTimeout(() => {
|
||||||
this.focused = false
|
this.focused = false
|
||||||
this.setCaret(e)
|
this.setCaret(e)
|
||||||
this.resize()
|
|
||||||
}, 200)
|
}, 200)
|
||||||
},
|
},
|
||||||
onClick (e, suggestion) {
|
onClick (e, suggestion) {
|
||||||
|
@ -428,18 +467,13 @@ 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.resize()
|
|
||||||
this.temporarilyHideSuggestions = false
|
this.temporarilyHideSuggestions = false
|
||||||
},
|
},
|
||||||
onKeyUp (e) {
|
onKeyUp (e) {
|
||||||
const { key } = e
|
const { key } = e
|
||||||
this.setCaret(e)
|
this.setCaret(e)
|
||||||
this.resize()
|
|
||||||
|
|
||||||
// Setting hider in keyUp to prevent suggestions from blinking
|
// Setting hider in keyUp to prevent suggestions from blinking
|
||||||
// when moving away from suggested spot
|
// when moving away from suggested spot
|
||||||
|
@ -451,7 +485,6 @@ const EmojiInput = {
|
||||||
},
|
},
|
||||||
onPaste (e) {
|
onPaste (e) {
|
||||||
this.setCaret(e)
|
this.setCaret(e)
|
||||||
this.resize()
|
|
||||||
},
|
},
|
||||||
onKeyDown (e) {
|
onKeyDown (e) {
|
||||||
const { ctrlKey, shiftKey, key } = e
|
const { ctrlKey, shiftKey, key } = e
|
||||||
|
@ -496,58 +529,21 @@ const EmojiInput = {
|
||||||
this.input.focus()
|
this.input.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showPicker = false
|
|
||||||
this.resize()
|
|
||||||
},
|
},
|
||||||
onInput (e) {
|
onInput (e) {
|
||||||
this.showPicker = false
|
|
||||||
this.setCaret(e)
|
this.setCaret(e)
|
||||||
this.resize()
|
|
||||||
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 } }) {
|
||||||
this.caret = selectionStart
|
this.caret = selectionStart
|
||||||
},
|
},
|
||||||
resize () {
|
resize () {
|
||||||
const panel = this.$refs.panel
|
|
||||||
if (!panel) return
|
|
||||||
const picker = this.$refs.picker.$el
|
|
||||||
const panelBody = this.$refs['panel-body']
|
|
||||||
const { offsetHeight, offsetTop } = this.input
|
|
||||||
const offsetBottom = offsetTop + offsetHeight
|
|
||||||
|
|
||||||
this.setPlacement(panelBody, panel, offsetBottom)
|
|
||||||
this.setPlacement(picker, picker, offsetBottom)
|
|
||||||
},
|
|
||||||
setPlacement (container, target, offsetBottom) {
|
|
||||||
if (!container || !target) return
|
|
||||||
|
|
||||||
target.style.top = offsetBottom + 'px'
|
|
||||||
target.style.bottom = 'auto'
|
|
||||||
|
|
||||||
if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
|
|
||||||
target.style.top = 'auto'
|
|
||||||
target.style.bottom = this.input.offsetHeight + 'px'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
overflowsBottom (el) {
|
|
||||||
return el.getBoundingClientRect().bottom > window.innerHeight
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
<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 }"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
<!-- TODO: make the 'x' disappear if at the end maybe? -->
|
||||||
|
<div class="hidden-overlay" :style="overlayStyle" ref="hiddenOverlay">
|
||||||
|
<span>{{ preText }}</span>
|
||||||
|
<span class="caret" ref="hiddenOverlayCaret">x</span>
|
||||||
|
<span>{{ postText }}</span>
|
||||||
|
</div>
|
||||||
<template v-if="enableEmojiPicker">
|
<template v-if="enableEmojiPicker">
|
||||||
<button
|
<button
|
||||||
v-if="!hideEmojiButton"
|
v-if="!hideEmojiButton"
|
||||||
|
@ -18,59 +23,61 @@
|
||||||
<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>
|
||||||
<div
|
<Popover
|
||||||
ref="panel"
|
|
||||||
class="autocomplete-panel"
|
class="autocomplete-panel"
|
||||||
:class="{ hide: !showSuggestions }"
|
placement="bottom"
|
||||||
|
ref="suggestorPopover"
|
||||||
>
|
>
|
||||||
<div
|
<template #content>
|
||||||
ref="panel-body"
|
|
||||||
class="autocomplete-panel-body"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-for="(suggestion, index) in suggestions"
|
ref="panel-body"
|
||||||
:key="index"
|
class="autocomplete-panel-body"
|
||||||
class="autocomplete-item"
|
|
||||||
:class="{ highlighted: index === highlighted }"
|
|
||||||
@click.stop.prevent="onClick($event, suggestion)"
|
|
||||||
>
|
>
|
||||||
<span class="image">
|
<div
|
||||||
<img
|
v-for="(suggestion, index) in suggestions"
|
||||||
v-if="suggestion.img"
|
:key="index"
|
||||||
:src="suggestion.img"
|
class="autocomplete-item"
|
||||||
>
|
:class="{ highlighted: index === highlighted }"
|
||||||
<span v-else>{{ suggestion.replacement }}</span>
|
@click.stop.prevent="onClick($event, suggestion)"
|
||||||
</span>
|
>
|
||||||
<div class="label">
|
<span class="image">
|
||||||
<span
|
<img
|
||||||
v-if="suggestion.user"
|
v-if="suggestion.img"
|
||||||
class="displayText"
|
:src="suggestion.img"
|
||||||
>
|
>
|
||||||
{{ suggestion.displayText }}<UnicodeDomainIndicator
|
<span v-else>{{ suggestion.replacement }}</span>
|
||||||
:user="suggestion.user"
|
|
||||||
:at="false"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<span
|
<div class="label">
|
||||||
v-if="!suggestion.user"
|
<span
|
||||||
class="displayText"
|
v-if="suggestion.user"
|
||||||
>
|
class="displayText"
|
||||||
{{ maybeLocalizedEmojiName(suggestion) }}
|
>
|
||||||
</span>
|
{{ suggestion.displayText }}<UnicodeDomainIndicator
|
||||||
<span class="detailText">{{ suggestion.detailText }}</span>
|
:user="suggestion.user"
|
||||||
|
:at="false"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="!suggestion.user"
|
||||||
|
class="displayText"
|
||||||
|
>
|
||||||
|
{{ maybeLocalizedEmojiName(suggestion) }}
|
||||||
|
</span>
|
||||||
|
<span class="detailText">{{ suggestion.detailText }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -102,6 +109,7 @@
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-panel {
|
.emoji-picker-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
@ -112,89 +120,83 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete {
|
|
||||||
&-panel {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 20;
|
|
||||||
margin-top: 2px;
|
|
||||||
|
|
||||||
&.hide {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
|
|
||||||
&-body {
|
|
||||||
margin: 0 0.5em 0 0.5em;
|
|
||||||
border-radius: $fallback--tooltipRadius;
|
|
||||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
|
||||||
box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
|
|
||||||
box-shadow: var(--popupShadow);
|
|
||||||
min-width: 75%;
|
|
||||||
background-color: $fallback--bg;
|
|
||||||
background-color: var(--popover, $fallback--bg);
|
|
||||||
color: $fallback--link;
|
|
||||||
color: var(--popoverText, $fallback--link);
|
|
||||||
--faint: var(--popoverFaintText, $fallback--faint);
|
|
||||||
--faintLink: var(--popoverFaintLink, $fallback--faint);
|
|
||||||
--lightText: var(--popoverLightText, $fallback--lightText);
|
|
||||||
--postLink: var(--popoverPostLink, $fallback--link);
|
|
||||||
--postFaintLink: var(--popoverPostFaintLink, $fallback--link);
|
|
||||||
--icon: var(--popoverIcon, $fallback--icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-item {
|
|
||||||
display: flex;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
.image {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 32px;
|
|
||||||
|
|
||||||
margin-right: 4px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 0 0.1em 0 0.2em;
|
|
||||||
|
|
||||||
.displayText {
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detailText {
|
|
||||||
font-size: 9px;
|
|
||||||
line-height: 9px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.highlighted {
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--selectedMenuPopover, $fallback--fg);
|
|
||||||
color: var(--selectedMenuPopoverText, $fallback--text);
|
|
||||||
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
|
|
||||||
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
|
|
||||||
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
|
|
||||||
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input, textarea {
|
input, textarea {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden-overlay {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
/* DEBUG STUFF */
|
||||||
|
color: red;
|
||||||
|
/* set opacity to non-zero to see the overlay */
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
width: 0;
|
||||||
|
margin-right: calc(-1ch - 1px);
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.autocomplete {
|
||||||
|
&-panel {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 32px;
|
||||||
|
|
||||||
|
margin-right: 4px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 0.1em 0 0.2em;
|
||||||
|
|
||||||
|
.displayText {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailText {
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.highlighted {
|
||||||
|
background-color: $fallback--fg;
|
||||||
|
background-color: var(--selectedMenuPopover, $fallback--fg);
|
||||||
|
color: var(--selectedMenuPopoverText, $fallback--text);
|
||||||
|
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
|
||||||
|
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
|
||||||
|
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
|
||||||
|
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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,32 @@ 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 () {
|
||||||
|
this.$refs.popover.showPopover()
|
||||||
|
this.onShowing()
|
||||||
|
},
|
||||||
|
hidePicker () {
|
||||||
|
this.$refs.popover.hidePopover()
|
||||||
|
},
|
||||||
|
setAnchorEl (el) {
|
||||||
|
this.$refs.popover.setAnchorEl(el)
|
||||||
|
},
|
||||||
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)
|
||||||
},
|
},
|
||||||
|
@ -128,6 +142,9 @@ const EmojiPicker = {
|
||||||
},
|
},
|
||||||
onEmoji (emoji) {
|
onEmoji (emoji) {
|
||||||
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
|
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
|
||||||
|
if (!this.keepOpen) {
|
||||||
|
this.$refs.popover.hidePopover()
|
||||||
|
}
|
||||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
||||||
},
|
},
|
||||||
onScroll (e) {
|
onScroll (e) {
|
||||||
|
@ -251,16 +268,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>
|
||||||
|
|
|
@ -51,6 +51,10 @@ const Popover = {
|
||||||
// lockReEntry is a flag that is set when mouse cursor is leaving the popover's content
|
// lockReEntry is a flag that is set when mouse cursor is leaving the popover's content
|
||||||
// so that if mouse goes back into popover it won't be re-shown again to prevent annoyance
|
// so that if mouse goes back into popover it won't be re-shown again to prevent annoyance
|
||||||
// with popovers refusing to be hidden when user wants to interact with something in below popover
|
// with popovers refusing to be hidden when user wants to interact with something in below popover
|
||||||
|
anchorEl: null,
|
||||||
|
// There's an issue where having teleport enabled by default causes things just...
|
||||||
|
// not render at all, i.e. main post status form and its emoji inputs
|
||||||
|
teleport: false,
|
||||||
lockReEntry: false,
|
lockReEntry: false,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
styles: {},
|
styles: {},
|
||||||
|
@ -59,10 +63,15 @@ 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()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
setAnchorEl (el) {
|
||||||
|
this.anchorEl = el
|
||||||
|
this.updateStyles()
|
||||||
|
},
|
||||||
containerBoundingClientRect () {
|
containerBoundingClientRect () {
|
||||||
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
|
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
|
||||||
return container.getBoundingClientRect()
|
return container.getBoundingClientRect()
|
||||||
|
@ -75,7 +84,7 @@ const Popover = {
|
||||||
|
|
||||||
// Popover will be anchored around this element, trigger ref is the container, so
|
// Popover will be anchored around this element, trigger ref is the container, so
|
||||||
// its children are what are inside the slot. Expect only one v-slot:trigger.
|
// its children are what are inside the slot. Expect only one v-slot:trigger.
|
||||||
const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
|
const anchorEl = this.anchorEl || (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
|
||||||
// SVGs don't have offsetWidth/Height, use fallback
|
// SVGs don't have offsetWidth/Height, use fallback
|
||||||
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
|
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
|
||||||
const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
|
const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
|
||||||
|
@ -226,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)
|
||||||
|
@ -286,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
|
||||||
|
@ -319,6 +333,7 @@ const Popover = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
this.teleport = true
|
||||||
let scrollable = this.$refs.trigger.closest('.column.-scrollable') ||
|
let scrollable = this.$refs.trigger.closest('.column.-scrollable') ||
|
||||||
this.$refs.trigger.closest('.mobile-notifications')
|
this.$refs.trigger.closest('.mobile-notifications')
|
||||||
if (!scrollable) scrollable = window
|
if (!scrollable) scrollable = window
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
>
|
>
|
||||||
<slot name="trigger" />
|
<slot name="trigger" />
|
||||||
</button>
|
</button>
|
||||||
<teleport to="#popovers">
|
<teleport :disabled="!teleport" to="#popovers">
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div
|
<div
|
||||||
v-if="!hidden"
|
v-if="!hidden"
|
||||||
|
|
|
@ -501,7 +501,6 @@ const PostStatusForm = {
|
||||||
if (target.value === '') {
|
if (target.value === '') {
|
||||||
target.style.height = null
|
target.style.height = null
|
||||||
this.$emit('resize')
|
this.$emit('resize')
|
||||||
this.$refs['emoji-input'].resize()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,8 +587,6 @@ const PostStatusForm = {
|
||||||
} else {
|
} else {
|
||||||
scrollerRef.scrollTop = targetScroll
|
scrollerRef.scrollTop = targetScroll
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$refs['emoji-input'].resize()
|
|
||||||
},
|
},
|
||||||
showEmojiPicker () {
|
showEmojiPicker () {
|
||||||
this.$refs.textarea.focus()
|
this.$refs.textarea.focus()
|
||||||
|
|
Loading…
Add table
Reference in a new issue