From 036882d27c22d768782c208a12625116ddc6ae7e Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 12 Aug 2019 13:18:37 +0300
Subject: rename for consistency

---
 src/components/emoji_input/emoji_input.vue | 167 +++++++++++++++++++++++++++++
 1 file changed, 167 insertions(+)
 create mode 100644 src/components/emoji_input/emoji_input.vue

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
new file mode 100644
index 00000000..3ca12af1
--- /dev/null
+++ b/src/components/emoji_input/emoji_input.vue
@@ -0,0 +1,167 @@
+<template>
+  <div
+    v-click-outside="onClickOutside"
+    class="emoji-input"
+  >
+    <slot />
+    <template v-if="emojiPicker">
+      <div
+        class="emoji-picker-icon"
+        :class="pickerIconBottom ? 'picker-icon-bottom': 'picker-icon-right'"
+        @click.prevent="togglePicker"
+      >
+        <i class="icon-smile" />
+      </div>
+      <EmojiPicker
+        v-if="emojiPicker"
+        ref="picker"
+        :class="{ hide: !showPicker }"
+        class="emoji-picker-panel"
+        @emoji="insert"
+      />
+    </template>
+    <div
+      ref="panel"
+      class="autocomplete-panel"
+      :class="{ hide: !showSuggestions }"
+    >
+      <div class="autocomplete-panel-body">
+        <div
+          v-for="(suggestion, index) in suggestions"
+          :key="index"
+          class="autocomplete-item"
+          :class="{ highlighted: suggestion.highlighted }"
+          @click.stop.prevent="onClick($event, suggestion)"
+        >
+          <span class="image">
+            <img
+              v-if="suggestion.img"
+              :src="suggestion.img"
+            >
+            <span v-else>{{ suggestion.replacement }}</span>
+          </span>
+          <div class="label">
+            <span class="displayText">{{ suggestion.displayText }}</span>
+            <span class="detailText">{{ suggestion.detailText }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script src="./emoji_input.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.emoji-input {
+  display: flex;
+  flex-direction: column;
+  position: relative;
+
+  .emoji-picker-icon {
+    position: absolute;
+    margin: 0 .25em;
+    font-size: 16px;
+    cursor: pointer;
+
+    &:hover i {
+      color: $fallback--text;
+      color: var(--text, $fallback--text);
+    }
+
+    &.picker-icon-bottom {
+      bottom: 0;
+      left: 0;
+    }
+    &.picker-icon-right {
+      top: 0;
+      right: 0;
+    }
+  }
+  .emoji-picker-panel {
+    position: absolute;
+    z-index: 9;
+    margin-top: 2px;
+
+    &.hide {
+      display: none
+    }
+  }
+
+  .autocomplete {
+    &-panel {
+      position: absolute;
+      z-index: 9;
+      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: $fallback--bg;
+        background: var(--bg, $fallback--bg);
+        color: $fallback--lightText;
+        color: var(--lightText, $fallback--lightText);
+      }
+    }
+
+    &-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(--lightBg, $fallback--fg);
+      }
+    }
+  }
+
+  input, textarea {
+    flex: 1 0 auto;
+  }
+}
+</style>
-- 
cgit v1.2.3-70-g09d2


From 5851f97eb058b3e2df91f9122ba899bc7e4affaf Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 12 Aug 2019 20:01:38 +0300
Subject: fixed a lot of bugs with emoji picker, improved relevant components

---
 src/components/emoji_input/emoji_input.js          | 33 ++++++++-
 src/components/emoji_input/emoji_input.vue         | 16 ++---
 src/components/emoji_picker/emoji_picker.js        | 82 +++++++++++++++++-----
 src/components/emoji_picker/emoji_picker.scss      | 78 +++++++++++++++-----
 src/components/emoji_picker/emoji_picker.vue       | 60 ++++++++++------
 .../post_status_form/post_status_form.js           | 25 ++-----
 .../post_status_form/post_status_form.vue          | 26 ++++---
 src/components/sticker_picker/sticker_picker.js    |  4 +-
 src/components/sticker_picker/sticker_picker.vue   | 72 +++++++++----------
 src/components/tab_switcher/tab_switcher.js        | 26 ++++++-
 src/components/tab_switcher/tab_switcher.scss      | 11 +++
 src/i18n/en.json                                   |  9 ++-
 12 files changed, 296 insertions(+), 146 deletions(-)

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index 5a9d1406..5ff27b20 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -58,6 +58,16 @@ const EmojiInput = {
       required: false,
       type: Boolean,
       default: false
+    },
+    emojiPickerExternalTrigger: {
+      required: false,
+      type: Boolean,
+      default: false
+    },
+    stickerPicker: {
+      required: false,
+      type: Boolean,
+      default: false
     }
   },
   data () {
@@ -95,9 +105,6 @@ const EmojiInput = {
     textAtCaret () {
       return (this.wordAtCaret || {}).word || ''
     },
-    pickerIconBottom () {
-      return this.input && this.input.tag === 'textarea'
-    },
     wordAtCaret () {
       if (this.value && this.caret) {
         const word = Completion.wordAtPosition(this.value, this.caret - 1) || {}
@@ -133,6 +140,9 @@ const EmojiInput = {
     }
   },
   methods: {
+    triggerShowPicker () {
+      this.showPicker = true
+    },
     togglePicker () {
       this.showPicker = !this.showPicker
     },
@@ -148,6 +158,15 @@ const EmojiInput = {
         this.value.substring(this.caret)
       ].join('')
       this.$emit('input', newValue)
+      const position = this.caret + insertion.length
+
+      this.$nextTick(function () {
+        // Re-focus inputbox after clicking suggestion
+        this.input.elm.focus()
+        // Set selection right after the replacement instead of the very end
+        this.input.elm.setSelectionRange(position, position)
+        this.caret = position
+      })
     },
     replaceText (e, suggestion) {
       const len = this.suggestions.length || 0
@@ -264,6 +283,14 @@ const EmojiInput = {
     onClickOutside () {
       this.showPicker = false
     },
+    onStickerUploaded (e) {
+      this.showPicker = false
+      this.$emit('sticker-uploaded', e)
+    },
+    onStickerUploadFailed (e) {
+      this.showPicker = false
+      this.$emit('sticker-upload-Failed', e)
+    },
     setCaret ({ target: { selectionStart } }) {
       this.caret = selectionStart
     },
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 3ca12af1..b077e6e9 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -6,8 +6,8 @@
     <slot />
     <template v-if="emojiPicker">
       <div
+        v-if="!emojiPickerExternalTrigger"
         class="emoji-picker-icon"
-        :class="pickerIconBottom ? 'picker-icon-bottom': 'picker-icon-right'"
         @click.prevent="togglePicker"
       >
         <i class="icon-smile" />
@@ -16,8 +16,11 @@
         v-if="emojiPicker"
         ref="picker"
         :class="{ hide: !showPicker }"
+        :sticker-picker="stickerPicker"
         class="emoji-picker-panel"
         @emoji="insert"
+        @sticker-uploaded="onStickerUploaded"
+        @sticker-upload-failed="onStickerUploadFailed"
       />
     </template>
     <div
@@ -62,6 +65,8 @@
 
   .emoji-picker-icon {
     position: absolute;
+    top: 0;
+    right: 0;
     margin: 0 .25em;
     font-size: 16px;
     cursor: pointer;
@@ -70,15 +75,6 @@
       color: $fallback--text;
       color: var(--text, $fallback--text);
     }
-
-    &.picker-icon-bottom {
-      bottom: 0;
-      left: 0;
-    }
-    &.picker-icon-right {
-      top: 0;
-      right: 0;
-    }
   }
   .emoji-picker-panel {
     position: absolute;
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index e25f98ff..0a64f759 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -1,15 +1,26 @@
+
 const filterByKeyword = (list, keyword = '') => {
   return list.filter(x => x.displayText.includes(keyword))
 }
 
 const EmojiPicker = {
+  props: {
+    stickerPicker: {
+      required: false,
+      type: Boolean,
+      default: false
+    }
+  },
   data () {
     return {
       keyword: '',
-      activeGroup: 'standard',
-      showingAdditional: false
+      activeGroup: 'custom',
+      showingStickers: false
     }
   },
+  components: {
+    StickerPicker: () => import('../sticker_picker/sticker_picker.vue')
+  },
   methods: {
     onEmoji (emoji) {
       const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
@@ -19,37 +30,72 @@ const EmojiPicker = {
     highlight (key) {
       const ref = this.$refs['group-' + key]
       const top = ref[0].offsetTop
-      this.$refs['emoji-groups'].scrollTop = top + 1
+      this.setShowStickers(false)
       this.activeGroup = key
+      this.$nextTick(() => {
+        this.$refs['emoji-groups'].scrollTop = top + 1
+      })
     },
     scrolledGroup (e) {
-      const top = e.target.scrollTop
-      Object.keys(this.emojis).forEach(key => {
-        if (this.$refs['group-' + key][0].offsetTop < top) {
-          this.activeGroup = key
-        }
+      const target = (e && e.target) || this.$refs['emoji-groups']
+      const top = target.scrollTop + 5
+      this.$nextTick(() => {
+        this.emojisView.forEach(group => {
+          const ref = this.$refs['group-' + group.id]
+          if (ref[0].offsetTop <= top) {
+            this.activeGroup = group.id
+          }
+        })
       })
     },
-    toggleAdditional (value) {
-      this.showingAdditional = value
+    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 {
-        custom: {
-          text: 'Custom',
-          icon: 'icon-picture',
+      return [
+        {
+          id: 'custom',
+          text: this.$t('emoji.custom'),
+          icon: 'icon-smile',
           emojis: filterByKeyword(customEmojis, this.keyword)
         },
-        standard: {
-          text: 'Standard',
-          icon: 'icon-star',
+        {
+          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)
     }
   }
 }
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 72889441..6c13e82b 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -1,39 +1,78 @@
 @import '../../_variables.scss';
 
 .emoji-picker {
+  display: flex;
+  flex-direction: column;
   position: absolute;
-  z-index: 1;
   right: 0;
-  width: 300px;
+  left: 0;
   height: 300px;
-  display: flex;
-  flex-direction: column;
   margin: 0 !important;
+  z-index: 1;
 
-  .emoji {
-    &-tabs {
-      &-item {
-        padding: 0 5px;
+  .panel-body {
+    display: flex;
+    flex-direction: column;
+    flex: 1 1 0;
+    min-height: 0px;
+  }
+
+  .additional-tabs {
+    border-left: 1px solid;
+    border-left-color: $fallback--icon;
+    border-left-color: var(--icon, $fallback--icon);
+    padding-left: 5px;
+    flex: 0 0 0;
+  }
+
+  .emoji-tabs {
+    flex: 1 1 0;
+  }
+
+  .additional-tabs,
+  .emoji-tabs {
+    &-item {
+      padding: 0 5px;
+      cursor: pointer;
+      font-size: 24px;
 
-        &.active {
-          border-bottom: 4px solid;
+      &.disabled {
+        opacity: 0.5;
+        pointer-events: none;
+      }
+      &.active {
+        border-bottom: 4px solid;
 
-          i {
-            color: $fallback--lightText;
-            color: var(--lightText, $fallback--lightText);
-          }
+        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 {
+        display: none
+      }
     }
+  }
 
+  .emoji {
     &-search {
       padding: 5px;
-      flex: 0 0 1px;
+      flex: 0 0 0;
 
       input {
         width: 100%;
@@ -50,13 +89,16 @@
       display: flex;
       align-items: center;
       flex-wrap: wrap;
-      padding: 5px;
-      justify-content: space-between;
+      padding-left: 5px;
+      justify-content: left;
 
       &-title {
         font-size: 12px;
         width: 100%;
         margin: 0;
+        &.disabled {
+          display: none;
+        }
       }
     }
 
@@ -68,7 +110,7 @@
       font-size: 32px;
       align-items: center;
       justify-content: center;
-      margin: 2px;
+      margin: 4px;
 
       cursor: pointer;
 
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index ec1702f3..12b1569e 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -3,30 +3,44 @@
     <div class="panel-heading">
       <span class="emoji-tabs">
         <span
-          v-for="(value, key) in emojis"
-          :key="key"
+          v-for="group in emojis"
+          :key="group.id"
           class="emoji-tabs-item"
-          :class="{'active': activeGroup === key}"
-          :title="value.text"
-          @click.prevent="highlight(key)"
+          :class="{
+            active: activeGroupView === group.id,
+            disabled: group.emojis.length === 0
+          }"
+          :title="group.text"
+          @click.prevent="highlight(group.id)"
         >
-          <i :class="value.icon" />
+          <i :class="group.icon" />
         </span>
       </span>
-      <span class="additional-tabs">
-        <slot name="tabs" />
+      <span
+        v-if="stickerPicker"
+        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="panel-body emoji-dropdown-menu-content">
+    <div class="panel-body">
       <div
-        v-if="!showingAdditional"
         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
@@ -35,22 +49,22 @@
           @scroll="scrolledGroup"
         >
           <div
-            v-for="(value, key) in emojis"
-            :key="key"
+            v-for="group in emojisView"
+            :key="group.id"
             class="emoji-group"
           >
             <h6
-              :ref="'group-' + key"
+              :ref="'group-' + group.id"
               class="emoji-group-title"
             >
-              {{ value.text }}
+              {{ group.text }}
             </h6>
             <span
-              v-for="emoji in value.emojis"
-              :key="key + emoji.displayText"
+              v-for="emoji in group.emojis"
+              :key="group.id + emoji.displayText"
               :title="emoji.displayText"
               class="emoji-item"
-              @click="onEmoji(emoji)"
+              @click.stop.prevent="onEmoji(emoji)"
             >
               <span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
               <img
@@ -61,11 +75,17 @@
           </div>
         </div>
       </div>
-      <div v-if="showingAdditional" class="additional-tabs-content">
-        <slot name="tab-content" />
+      <div
+        v-if="showingStickers"
+        class="stickers-content"
+      >
+        <sticker-picker
+          @uploaded="onStickerUploaded"
+          @upload-failed="onStickerUploadFailed"
+        />
       </div>
     </div>
-</div>
+  </div>
 </template>
 
 <script src="./emoji_picker.js"></script>
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index f646aeb5..1359e75a 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -3,7 +3,6 @@ import MediaUpload from '../media_upload/media_upload.vue'
 import ScopeSelector from '../scope_selector/scope_selector.vue'
 import EmojiInput from '../emoji_input/emoji_input.vue'
 import PollForm from '../poll/poll_form.vue'
-import StickerPicker from '../sticker_picker/sticker_picker.vue'
 import fileTypeService from '../../services/file_type/file_type.service.js'
 import { reject, map, uniqBy } from 'lodash'
 import suggestor from '../emoji_input/suggestor.js'
@@ -35,7 +34,6 @@ const PostStatusForm = {
     MediaUpload,
     EmojiInput,
     PollForm,
-    StickerPicker,
     ScopeSelector
   },
   mounted () {
@@ -84,8 +82,7 @@ const PostStatusForm = {
         contentType
       },
       caret: 0,
-      pollFormVisible: false,
-      stickerPickerVisible: false
+      pollFormVisible: false
     }
   },
   computed: {
@@ -161,12 +158,6 @@ const PostStatusForm = {
     safeDMEnabled () {
       return this.$store.state.instance.safeDM
     },
-    stickersAvailable () {
-      if (this.$store.state.instance.stickers) {
-        return this.$store.state.instance.stickers.length > 0
-      }
-      return 0
-    },
     pollsAvailable () {
       return this.$store.state.instance.pollsAvailable &&
         this.$store.state.instance.pollLimits.max_options >= 2
@@ -222,7 +213,6 @@ const PostStatusForm = {
             poll: {}
           }
           this.pollFormVisible = false
-          this.stickerPickerVisible = false
           this.$refs.mediaUpload.clearFile()
           this.clearPollForm()
           this.$emit('posted')
@@ -239,7 +229,6 @@ const PostStatusForm = {
     addMediaFile (fileInfo) {
       this.newStatus.files.push(fileInfo)
       this.enableSubmit()
-      this.stickerPickerVisible = false
     },
     removeMediaFile (fileInfo) {
       let index = this.newStatus.files.indexOf(fileInfo)
@@ -293,20 +282,16 @@ const PostStatusForm = {
         target.style.height = null
       }
     },
+    showEmoji () {
+      this.$refs['textarea'].focus()
+      this.$refs['emoji-input'].triggerShowPicker()
+    },
     clearError () {
       this.error = null
     },
     changeVis (visibility) {
       this.newStatus.visibility = visibility
     },
-    toggleStickerPicker () {
-      this.stickerPickerVisible = !this.stickerPickerVisible
-    },
-    clearStickerPicker () {
-      if (this.$refs.stickerPicker) {
-        this.$refs.stickerPicker.clear()
-      }
-    },
     togglePollForm () {
       this.pollFormVisible = !this.pollFormVisible
     },
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index e691acad..ad2c2218 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -74,10 +74,15 @@
           >
         </EmojiInput>
         <EmojiInput
+          ref="emoji-input"
           v-model="newStatus.status"
           :suggest="emojiUserSuggestor"
-          emoji-picker
           class="form-control main-input"
+          emoji-picker
+          emoji-picker-external-trigger
+          sticker-picker
+          @sticker-uploaded="addMediaFile"
+          @sticker-upload-failed="uploadFailed"
         >
           <textarea
             ref="textarea"
@@ -160,14 +165,12 @@
             @upload-failed="uploadFailed"
           />
           <div
-            v-if="stickersAvailable"
-            class="sticker-icon"
+            class="emoji-icon"
           >
             <i
-              :title="$t('stickers.add_sticker')"
-              class="icon-picture btn btn-default"
-              :class="{ selected: stickerPickerVisible }"
-              @click="toggleStickerPicker"
+              :title="$t('emoji.add_emoji')"
+              class="icon-smile btn btn-default"
+              @click.stop.prevent="showEmoji"
             />
           </div>
           <div
@@ -260,11 +263,6 @@
         <label for="filesSensitive">{{ $t('post_status.attachments_sensitive') }}</label>
       </div>
     </form>
-    <sticker-picker
-      v-if="stickerPickerVisible"
-      ref="stickerPicker"
-      @uploaded="addMediaFile"
-    />
   </div>
 </template>
 
@@ -327,7 +325,7 @@
     }
   }
 
-  .poll-icon, .sticker-icon {
+  .poll-icon, .emoji-icon {
     font-size: 26px;
     flex: 1;
 
@@ -337,7 +335,7 @@
     }
   }
 
-  .sticker-icon {
+  .emoji-icon {
     flex: 0;
     min-width: 50px;
   }
diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js
index a6dcded3..8daf3f07 100644
--- a/src/components/sticker_picker/sticker_picker.js
+++ b/src/components/sticker_picker/sticker_picker.js
@@ -3,9 +3,9 @@ import statusPosterService from '../../services/status_poster/status_poster.serv
 import TabSwitcher from '../tab_switcher/tab_switcher.js'
 
 const StickerPicker = {
-  components: [
+  components: {
     TabSwitcher
-  ],
+  },
   data () {
     return {
       meta: {
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
index 938204c8..323855b9 100644
--- a/src/components/sticker_picker/sticker_picker.vue
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -2,32 +2,30 @@
   <div
     class="sticker-picker"
   >
-    <div
-      class="sticker-picker-panel"
+    <tab-switcher
+      class="tab-switcher"
+      :render-only-focused="true"
+      scrollable-tabs
     >
-      <tab-switcher
-        :render-only-focused="true"
+      <div
+        v-for="stickerpack in pack"
+        :key="stickerpack.path"
+        :image-tooltip="stickerpack.meta.title"
+        :image="stickerpack.path + stickerpack.meta.tabIcon"
+        class="sticker-picker-content"
       >
         <div
-          v-for="stickerpack in pack"
-          :key="stickerpack.path"
-          :image-tooltip="stickerpack.meta.title"
-          :image="stickerpack.path + stickerpack.meta.tabIcon"
-          class="sticker-picker-content"
+          v-for="sticker in stickerpack.meta.stickers"
+          :key="sticker"
+          class="sticker"
+          @click.stop.prevent="pick(stickerpack.path + sticker, stickerpack.meta.title)"
         >
-          <div
-            v-for="sticker in stickerpack.meta.stickers"
-            :key="sticker"
-            class="sticker"
-            @click="pick(stickerpack.path + sticker, stickerpack.meta.title)"
+          <img
+            :src="stickerpack.path + sticker"
           >
-            <img
-              :src="stickerpack.path + sticker"
-            >
-          </div>
         </div>
-      </tab-switcher>
-    </div>
+      </div>
+    </tab-switcher>
   </div>
 </template>
 
@@ -37,22 +35,24 @@
 @import '../../_variables.scss';
 
 .sticker-picker {
-  .sticker-picker-panel {
-    display: inline-block;
-    width: 100%;
-    .sticker-picker-content {
-      max-height: 300px;
-      overflow-y: scroll;
-      overflow-x: auto;
-      .sticker {
-        display: inline-block;
-        width: 20%;
-        height: 20%;
-        img {
-          width: 100%;
-          &:hover {
-            filter: drop-shadow(0 0 5px var(--link, $fallback--link));
-          }
+  width: 100%;
+  position: relative;
+  .tab-switcher {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+  }
+  .sticker-picker-content {
+    .sticker {
+      display: inline-block;
+      width: 20%;
+      height: 20%;
+      img {
+        width: 100%;
+        &:hover {
+          filter: drop-shadow(0 0 5px var(--link, $fallback--link));
         }
       }
     }
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index a5fe019c..99428044 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -4,7 +4,26 @@ import './tab_switcher.scss'
 
 export default Vue.component('tab-switcher', {
   name: 'TabSwitcher',
-  props: ['renderOnlyFocused', 'onSwitch', 'customActive'],
+  props: {
+    renderOnlyFocused: {
+      required: false,
+      type: Boolean,
+      default: false
+    },
+    onSwitch: {
+      required: false,
+      type: Function
+    },
+    customActive: {
+      required: false,
+      type: String
+    },
+    scrollableTabs: {
+      required: false,
+      type: Boolean,
+      default: false
+    }
+  },
   data () {
     return {
       active: this.$slots.default.findIndex(_ => _.tag)
@@ -18,7 +37,8 @@ export default Vue.component('tab-switcher', {
   },
   methods: {
     activateTab (index, dataset) {
-      return () => {
+      return (e) => {
+        e.preventDefault()
         if (typeof this.onSwitch === 'function') {
           this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset)
         }
@@ -85,7 +105,7 @@ export default Vue.component('tab-switcher', {
         <div class="tabs">
           {tabs}
         </div>
-        <div class="contents">
+        <div class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}>
           {contents}
         </div>
       </div>
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index 4eeb42e0..3e5eacd5 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -1,10 +1,21 @@
 @import '../../_variables.scss';
 
 .tab-switcher {
+  display: flex;
+  flex-direction: column;
+
   .contents {
+    flex: 1 0 auto;
+    min-height: 0px;
+
     .hidden {
       display: none;
     }
+
+    &.scrollable-tabs {
+      flex-basis: 0;
+      overflow-y: auto;
+    }
   }
   .tabs {
     display: flex;
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 60a3e284..13f7168f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -106,8 +106,13 @@
     "expired": "Poll ended {0} ago",
     "not_enough_options": "Too few unique options in poll"
   },
-  "stickers": {
-    "add_sticker": "Add Sticker"
+  "emoji": {
+    "stickers": "Stickers",
+    "emoji": "Emoji",
+    "search_emoji": "Search for an emoji",
+    "add_emoji": "Insert emoji",
+    "custom": "Custom emoji",
+    "unicode": "Unicode emoji"
   },
   "interactions": {
     "favs_repeats": "Repeats and Favorites",
-- 
cgit v1.2.3-70-g09d2


From 3505e53756562b4d7b304d0da9910c4528b25959 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 12 Sep 2019 20:36:43 +0300
Subject: review/naming + bugfix for stickers

---
 src/components/emoji_input/emoji_input.js            | 17 ++++++++++++++---
 src/components/emoji_input/emoji_input.vue           |  8 ++++----
 src/components/emoji_picker/emoji_picker.js          |  5 ++++-
 src/components/emoji_picker/emoji_picker.vue         |  2 +-
 src/components/post_status_form/post_status_form.vue |  8 ++++----
 5 files changed, 27 insertions(+), 13 deletions(-)

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index 6dc006f8..94af6e2f 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -54,17 +54,28 @@ const EmojiInput = {
       required: true,
       type: String
     },
-    emojiPicker: {
+    enableEmojiPicker: {
+      /**
+       * Enables emoji picker support, this implies that custom emoji are supported
+       */
       required: false,
       type: Boolean,
       default: false
     },
-    emojiPickerExternalTrigger: {
+    hideEmojiButton: {
+      /**
+    enableStickerPicker: {
+       * intended to use with external picker trigger, i.e. you have a button outside
+       * input that will open up the picker, see triggerShowPicker()
+       */
       required: false,
       type: Boolean,
       default: false
     },
-    stickerPicker: {
+    enableStickerPicker: {
+      /**
+       * Enables sticker picker support, only makes sense when enableEmojiPicker=true
+       */
       required: false,
       type: Boolean,
       default: false
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index b077e6e9..53b38573 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -4,19 +4,19 @@
     class="emoji-input"
   >
     <slot />
-    <template v-if="emojiPicker">
+    <template v-if="enableEmojiPicker">
       <div
-        v-if="!emojiPickerExternalTrigger"
+        v-if="!hideEmojiButton"
         class="emoji-picker-icon"
         @click.prevent="togglePicker"
       >
         <i class="icon-smile" />
       </div>
       <EmojiPicker
-        v-if="emojiPicker"
+        v-if="enableEmojiPicker"
         ref="picker"
         :class="{ hide: !showPicker }"
-        :sticker-picker="stickerPicker"
+        :enable-sticker-picker="enableStickerPicker"
         class="emoji-picker-panel"
         @emoji="insert"
         @sticker-uploaded="onStickerUploaded"
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index bce5026e..570ace13 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -5,7 +5,7 @@ const filterByKeyword = (list, keyword = '') => {
 
 const EmojiPicker = {
   props: {
-    stickerPicker: {
+    enableStickerPicker: {
       required: false,
       type: Boolean,
       default: false
@@ -97,6 +97,9 @@ const EmojiPicker = {
     },
     emojisView () {
       return this.emojis.filter(value => value.emojis.length > 0)
+    },
+    stickerPickerEnabled () {
+      return (this.$store.state.instance.stickers || []).length !== 0
     }
   }
 }
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index 901520aa..5c139d0e 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -17,7 +17,7 @@
         </span>
       </span>
       <span
-        v-if="stickerPicker"
+        v-if="stickerPickerEnabled"
         class="additional-tabs"
       >
         <span
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 026cb8fe..0e0b0e60 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -61,7 +61,7 @@
         <EmojiInput
           v-if="newStatus.spoilerText || alwaysShowSubject"
           v-model="newStatus.spoilerText"
-          emoji-picker
+          enable-emoji-picker
           :suggest="emojiSuggestor"
           class="form-control"
         >
@@ -78,9 +78,9 @@
           v-model="newStatus.status"
           :suggest="emojiUserSuggestor"
           class="form-control main-input"
-          emoji-picker
-          emoji-picker-external-trigger
-          sticker-picker
+          enable-emoji-picker
+          hide-emoji-button
+          enable-sticker-picker
           @sticker-uploaded="addMediaFile"
           @sticker-upload-failed="uploadFailed"
         >
-- 
cgit v1.2.3-70-g09d2


From 0b300e170805f45bd8cc854cbfa48b4df020b909 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 25 Sep 2019 20:01:06 +0300
Subject: bump z-index so that picker/suggest doesn't get overlapped by mobile
 button

---
 src/components/emoji_input/emoji_input.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 53b38573..05fb931b 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -78,7 +78,7 @@
   }
   .emoji-picker-panel {
     position: absolute;
-    z-index: 9;
+    z-index: 20;
     margin-top: 2px;
 
     &.hide {
@@ -89,7 +89,7 @@
   .autocomplete {
     &-panel {
       position: absolute;
-      z-index: 9;
+      z-index: 20;
       margin-top: 2px;
 
       &.hide {
-- 
cgit v1.2.3-70-g09d2


From e6d2cf77af9ac2119c23e8c748368f0fdceae0ae Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 25 Sep 2019 20:11:06 +0300
Subject: fix emoji inputs in user-settings, styles update

---
 src/components/emoji_input/emoji_input.vue     | 3 ++-
 src/components/user_settings/user_settings.vue | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

(limited to 'src/components/emoji_input/emoji_input.vue')

diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 05fb931b..13530e8b 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -67,9 +67,10 @@
     position: absolute;
     top: 0;
     right: 0;
-    margin: 0 .25em;
+    margin: .2em .25em;
     font-size: 16px;
     cursor: pointer;
+    line-height: 24px;
 
     &:hover i {
       color: $fallback--text;
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 34ea8569..97833acb 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -32,6 +32,7 @@
             <p>{{ $t('settings.name') }}</p>
             <EmojiInput
               v-model="newName"
+              enable-emoji-picker
               :suggest="emojiSuggestor"
             >
               <input
@@ -43,6 +44,7 @@
             <p>{{ $t('settings.bio') }}</p>
             <EmojiInput
               v-model="newBio"
+              enable-emoji-picker
               :suggest="emojiUserSuggestor"
             >
               <textarea
-- 
cgit v1.2.3-70-g09d2

