Browse Source

Allow custom emoji reactions

Alexander Tumin 2 years ago
parent
commit
998aa8f732

+ 25 - 2
src/components/emoji_reactions/emoji_reactions.vue

@@ -2,7 +2,7 @@
   <div class="EmojiReactions">
     <UserListPopover
       v-for="(reaction) in emojiReactions"
-      :key="reaction.name"
+      :key="reaction.url || reaction.name"
       :users="accountsForEmoji[reaction.name]"
     >
       <button
@@ -11,7 +11,21 @@
         @click="emojiOnClick(reaction.name, $event)"
         @mouseenter="fetchEmojiReactionsByIfMissing()"
       >
-        <span class="reaction-emoji">{{ reaction.name }}</span>
+        <span
+          v-if="reaction.url"
+          class="reaction-emoji"
+        >
+          <img
+            :src="reaction.url"
+            :title="reaction.name"
+            class="reaction-emoji-content"
+            width="1em"
+          >
+        </span>
+        <span
+          v-else
+          class="reaction-emoji reaction-emoji-content"
+        >{{ reaction.name }}</span>
         <span>{{ reaction.count }}</span>
       </button>
     </UserListPopover>
@@ -46,9 +60,18 @@
 
     .reaction-emoji {
       width: 1.25em;
+      height: 1.25em;
       margin-right: 0.25em;
     }
 
+    .reaction-emoji-content {
+      max-width: 1.25em;
+      max-height: 1.25em;
+      width: auto;
+      height: auto;
+      overflow: hidden;
+    }
+
     &:focus {
       outline: none;
     }

+ 12 - 3
src/components/notification/notification.vue

@@ -121,7 +121,16 @@
                   scope="global"
                   keypath="notifications.reacted_with"
                 >
-                  <span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
+                  <img
+                    v-if="notification.emoji_url"
+                    class="emoji-reaction-emoji emoji-reaction-emoji-image"
+                    :src="notification.emoji_url"
+                    :name="notification.emoji"
+                  >
+                  <span
+                    v-else
+                    class="emoji-reaction-emoji"
+                  >{{ notification.emoji }}</span>
                 </i18n-t>
               </small>
             </span>
@@ -153,9 +162,9 @@
             </router-link>
             <button
               class="button-unstyled expand-icon"
-              @click.prevent="toggleStatusExpanded"
-              :title="$t('tool_tip.toggle_expand')"
               :aria-expanded="statusExpanded"
+              :title="$t('tool_tip.toggle_expand')"
+              @click.prevent="toggleStatusExpanded"
             >
               <FAIcon
                 class="fa-scale-110"

+ 7 - 0
src/components/notifications/notifications.scss

@@ -129,6 +129,13 @@
 
   .emoji-reaction-emoji {
     font-size: 1.3em;
+    max-width: 1.25em;
+    height: 1.25em;
+    width: auto;
+  }
+
+  .emoji-reaction-emoji-image {
+    vertical-align: middle;
   }
 
   .notification-details {

+ 10 - 82
src/components/react_button/react_button.js

@@ -1,9 +1,8 @@
 import Popover from '../popover/popover.vue'
-import { ensureFinalFallback } from '../../i18n/languages.js'
+import EmojiPicker from '../emoji_picker/emoji_picker.vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import { faPlus, faTimes } from '@fortawesome/free-solid-svg-icons'
 import { faSmileBeam } from '@fortawesome/free-regular-svg-icons'
-import { trim } from 'lodash'
 
 library.add(
   faPlus,
@@ -20,103 +19,32 @@ const ReactButton = {
     }
   },
   components: {
-    Popover
+    Popover,
+    EmojiPicker
   },
   methods: {
-    addReaction (event, emoji, close) {
+    addReaction (event) {
+      const emoji = event.insertion
       const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
       if (existingReaction && existingReaction.me) {
         this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
       } else {
         this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
       }
-      close()
+    },
+    show () {
+      if (!this.expanded) {
+        this.$refs.picker.showPicker()
+      }
     },
     onShow () {
       this.expanded = true
-      this.focusInput()
     },
     onClose () {
       this.expanded = false
-    },
-    focusInput () {
-      this.$nextTick(() => {
-        const input = document.querySelector('.reaction-picker-filter > input')
-        if (input) input.focus()
-      })
-    },
-    // Vaguely adjusted copypaste from emoji_input and emoji_picker!
-    maybeLocalizedEmojiNamesAndKeywords (emoji) {
-      const names = [emoji.displayText]
-      const keywords = []
-
-      if (emoji.displayTextI18n) {
-        names.push(this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args))
-      }
-
-      if (emoji.annotations) {
-        this.languages.forEach(lang => {
-          names.push(emoji.annotations[lang]?.name)
-
-          keywords.push(...(emoji.annotations[lang]?.keywords || []))
-        })
-      }
-
-      return {
-        names: names.filter(k => k),
-        keywords: keywords.filter(k => k)
-      }
-    },
-    maybeLocalizedEmojiName (emoji) {
-      if (!emoji.annotations) {
-        return emoji.displayText
-      }
-
-      if (emoji.displayTextI18n) {
-        return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
-      }
-
-      for (const lang of this.languages) {
-        if (emoji.annotations[lang]?.name) {
-          return emoji.annotations[lang].name
-        }
-      }
-
-      return emoji.displayText
     }
   },
   computed: {
-    commonEmojis () {
-      const hardcodedSet = new Set(['👍', '😠', '👀', '😂', '🔥'])
-      return this.$store.getters.standardEmojiList.filter(emoji => hardcodedSet.has(emoji.replacement))
-    },
-    languages () {
-      return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
-    },
-    emojis () {
-      if (this.filterWord !== '') {
-        const keywordLowercase = trim(this.filterWord.toLowerCase())
-
-        const orderedEmojiList = []
-        for (const emoji of this.$store.getters.standardEmojiList) {
-          const indices = this.maybeLocalizedEmojiNamesAndKeywords(emoji)
-            .keywords
-            .map(k => k.toLowerCase().indexOf(keywordLowercase))
-            .filter(k => k > -1)
-
-          const indexOfKeyword = indices.length ? Math.min(...indices) : -1
-
-          if (indexOfKeyword > -1) {
-            if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
-              orderedEmojiList[indexOfKeyword] = []
-            }
-            orderedEmojiList[indexOfKeyword].push(emoji)
-          }
-        }
-        return orderedEmojiList.flat()
-      }
-      return this.$store.getters.standardEmojiList || []
-    },
     mergedConfig () {
       return this.$store.getters.mergedConfig
     }

+ 34 - 74
src/components/react_button/react_button.vue

@@ -1,73 +1,38 @@
 <template>
-  <Popover
-    trigger="click"
-    class="ReactButton"
-    placement="top"
-    :offset="{ y: 5 }"
-    :bound-to="{ x: 'container' }"
-    remove-padding
-    popover-class="ReactButton popover-default"
-    @show="onShow"
-    @close="onClose"
-  >
-    <template #content="{close}">
-      <div class="reaction-picker-filter">
-        <input
-          v-model="filterWord"
-          size="1"
-          :placeholder="$t('emoji.search_emoji')"
-          @input="$event.target.composing = false"
-        >
-      </div>
-      <div class="reaction-picker">
-        <span
-          v-for="emoji in commonEmojis"
-          :key="emoji.replacement"
-          class="emoji-button"
-          :title="maybeLocalizedEmojiName(emoji)"
-          @click="addReaction($event, emoji.replacement, close)"
-        >
-          {{ emoji.replacement }}
-        </span>
-        <div class="reaction-picker-divider" />
-        <span
-          v-for="(emoji, key) in emojis"
-          :key="key"
-          class="emoji-button"
-          :title="maybeLocalizedEmojiName(emoji)"
-          @click="addReaction($event, emoji.replacement, close)"
-        >
-          {{ emoji.replacement }}
-        </span>
-        <div class="reaction-bottom-fader" />
-      </div>
-    </template>
-    <template #trigger>
-      <span
-        class="button-unstyled popover-trigger"
-        :title="$t('tool_tip.add_reaction')"
-      >
-        <FALayers>
-          <FAIcon
-            class="fa-scale-110 fa-old-padding"
-            :icon="['far', 'smile-beam']"
-          />
-          <FAIcon
-            v-show="!expanded"
-            class="focus-marker"
-            transform="shrink-6 up-9 right-17"
-            icon="plus"
-          />
-          <FAIcon
-            v-show="expanded"
-            class="focus-marker"
-            transform="shrink-6 up-9 right-17"
-            icon="times"
-          />
-        </FALayers>
-      </span>
-    </template>
-  </Popover>
+  <span class="ReactButton">
+    <EmojiPicker
+      ref="picker"
+      :enable-sticker-picker="enableStickerPicker"
+      class="emoji-picker-panel"
+      @emoji="addReaction"
+      @show="onShow"
+      @close="onClose"
+    />
+    <span
+      class="button-unstyled popover-trigger"
+      :title="$t('tool_tip.add_reaction')"
+      @click.stop.prevent="show"
+    >
+      <FALayers>
+        <FAIcon
+          class="fa-scale-110 fa-old-padding"
+          :icon="['far', 'smile-beam']"
+        />
+        <FAIcon
+          v-show="!expanded"
+          class="focus-marker"
+          transform="shrink-6 up-9 right-17"
+          icon="plus"
+        />
+        <FAIcon
+          v-show="expanded"
+          class="focus-marker"
+          transform="shrink-6 up-9 right-17"
+          icon="times"
+        />
+      </FALayers>
+    </span>
+  </span>
 </template>
 
 <script src="./react_button.js"></script>
@@ -135,11 +100,6 @@
       color: $fallback--text;
       color: var(--text, $fallback--text);
     }
-  }
-
-  .popover-trigger-button {
-    /* override of popover internal stuff */
-    width: auto;
 
     @include unfocused-style {
       .focus-marker {

+ 1 - 0
src/services/entity_normalizer/entity_normalizer.service.js

@@ -441,6 +441,7 @@ export const parseNotification = (data) => {
       : parseUser(data.target)
     output.from_profile = parseUser(data.account)
     output.emoji = data.emoji
+    output.emoji_url = data.emoji_url
     if (data.report) {
       output.report = data.report
       output.report.content = data.report.content