diff options
Diffstat (limited to 'src/components/emoji_picker')
| -rw-r--r-- | src/components/emoji_picker/emoji_picker.js | 115 | ||||
| -rw-r--r-- | src/components/emoji_picker/emoji_picker.scss | 165 | ||||
| -rw-r--r-- | src/components/emoji_picker/emoji_picker.vue | 110 |
3 files changed, 390 insertions, 0 deletions
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js new file mode 100644 index 00000000..824412dd --- /dev/null +++ b/src/components/emoji_picker/emoji_picker.js @@ -0,0 +1,115 @@ + +const filterByKeyword = (list, keyword = '') => { + return list.filter(x => x.displayText.includes(keyword)) +} + +const EmojiPicker = { + props: { + enableStickerPicker: { + required: false, + type: Boolean, + default: false + } + }, + data () { + return { + labelKey: String(Math.random() * 100000), + keyword: '', + activeGroup: 'custom', + showingStickers: false, + groupsScrolledClass: 'scrolled-top', + keepOpen: false + } + }, + components: { + StickerPicker: () => import('../sticker_picker/sticker_picker.vue') + }, + methods: { + onEmoji (emoji) { + const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement + this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen }) + }, + highlight (key) { + const ref = this.$refs['group-' + key] + const top = ref[0].offsetTop + this.setShowStickers(false) + this.activeGroup = key + this.$nextTick(() => { + this.$refs['emoji-groups'].scrollTop = top + 1 + }) + }, + scrolledGroup (e) { + const target = (e && e.target) || this.$refs['emoji-groups'] + const top = target.scrollTop + 5 + if (target.scrollTop <= 5) { + this.groupsScrolledClass = 'scrolled-top' + } else if (target.scrollTop >= target.scrollTopMax - 5) { + this.groupsScrolledClass = 'scrolled-bottom' + } else { + this.groupsScrolledClass = 'scrolled-middle' + } + this.$nextTick(() => { + this.emojisView.forEach(group => { + const ref = this.$refs['group-' + group.id] + if (ref[0].offsetTop <= top) { + this.activeGroup = group.id + } + }) + }) + }, + toggleStickers () { + this.showingStickers = !this.showingStickers + }, + setShowStickers (value) { + this.showingStickers = value + }, + onStickerUploaded (e) { + this.$emit('sticker-uploaded', e) + }, + onStickerUploadFailed (e) { + this.$emit('sticker-upload-failed', e) + } + }, + watch: { + keyword () { + this.scrolledGroup() + } + }, + computed: { + activeGroupView () { + return this.showingStickers ? '' : this.activeGroup + }, + stickersAvailable () { + if (this.$store.state.instance.stickers) { + return this.$store.state.instance.stickers.length > 0 + } + return 0 + }, + emojis () { + const standardEmojis = this.$store.state.instance.emoji || [] + const customEmojis = this.$store.state.instance.customEmoji || [] + return [ + { + id: 'custom', + text: this.$t('emoji.custom'), + icon: 'icon-smile', + emojis: filterByKeyword(customEmojis, this.keyword) + }, + { + id: 'standard', + text: this.$t('emoji.unicode'), + icon: 'icon-picture', + emojis: filterByKeyword(standardEmojis, this.keyword) + } + ] + }, + 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.scss b/src/components/emoji_picker/emoji_picker.scss new file mode 100644 index 00000000..b0ed00e9 --- /dev/null +++ b/src/components/emoji_picker/emoji_picker.scss @@ -0,0 +1,165 @@ +@import '../../_variables.scss'; + +.emoji-picker { + display: flex; + flex-direction: column; + position: absolute; + right: 0; + left: 0; + height: 320px; + margin: 0 !important; + z-index: 1; + + .keep-open { + padding: 7px; + line-height: normal; + } + .keep-open-label { + padding: 0 7px; + display: flex; + } + + .heading { + display: flex; + height: 32px; + padding: 10px 7px 5px; + } + + .content { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-height: 0px; + } + + .emoji-tabs { + flex-grow: 1; + } + + .additional-tabs { + border-left: 1px solid; + border-left-color: $fallback--icon; + border-left-color: var(--icon, $fallback--icon); + padding-left: 7px; + flex: 0 0 0; + } + + .additional-tabs, + .emoji-tabs { + display: block; + min-width: 0; + flex-basis: auto; + flex-shrink: 1; + + &-item { + padding: 0 7px; + cursor: pointer; + font-size: 24px; + + &.disabled { + opacity: 0.5; + pointer-events: none; + } + &.active { + border-bottom: 4px solid; + + i { + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); + } + } + } + } + + .sticker-picker { + flex: 1 1 0 + } + + .stickers, + .emoji { + &-content { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-height: 0; + + &.hidden { + opacity: 0; + pointer-events: none; + position: absolute; + } + } + } + + .emoji { + &-search { + padding: 5px; + flex: 0 0 0; + + input { + width: 100%; + } + } + + &-groups { + flex: 1 1 1px; + position: relative; + overflow: auto; + user-select: none; + mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat, + linear-gradient(to bottom, white 0, transparent 100%) top no-repeat, + linear-gradient(to top, white, white); + transition: mask-size 150ms; + mask-size: 100% 20px, 100% 20px, auto; + // Autoprefixed seem to ignore this one, and also syntax is different + -webkit-mask-composite: xor; + mask-composite: exclude; + &.scrolled { + &-top { + mask-size: 100% 20px, 100% 0, auto; + } + &-bottom { + mask-size: 100% 0, 100% 20px, auto; + } + } + } + + &-group { + display: flex; + align-items: center; + flex-wrap: wrap; + padding-left: 5px; + justify-content: left; + + &-title { + font-size: 12px; + width: 100%; + margin: 0; + &.disabled { + display: none; + } + } + } + + &-item { + width: 32px; + height: 32px; + box-sizing: border-box; + display: flex; + font-size: 32px; + align-items: center; + justify-content: center; + margin: 4px; + + cursor: pointer; + + img { + object-fit: contain; + max-width: 100%; + max-height: 100%; + } + } + + } + +} diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue new file mode 100644 index 00000000..42f20130 --- /dev/null +++ b/src/components/emoji_picker/emoji_picker.vue @@ -0,0 +1,110 @@ +<template> + <div class="emoji-picker panel panel-default panel-body"> + <div class="heading"> + <span class="emoji-tabs"> + <span + v-for="group in emojis" + :key="group.id" + class="emoji-tabs-item" + :class="{ + active: activeGroupView === group.id, + disabled: group.emojis.length === 0 + }" + :title="group.text" + @click.prevent="highlight(group.id)" + > + <i :class="group.icon" /> + </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" + > + <i class="icon-star" /> + </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')" + > + </div> + <div + ref="emoji-groups" + class="emoji-groups" + :class="groupsScrolledClass" + @scroll="scrolledGroup" + > + <div + v-for="group in emojisView" + :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 + v-else + :src="emoji.imageUrl" + > + </span> + </div> + </div> + <div + class="keep-open" + > + <input + :id="labelKey + 'keep-open'" + v-model="keepOpen" + type="checkbox" + > + <label + class="keep-open-label" + :for="labelKey + 'keep-open'" + > + <div class="keep-open-label-text"> + {{ $t('emoji.keep_open') }} + </div> + </label> + </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> |
