From 7f513fe46fbf631190445b21b7cb4b7cb431d019 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 20 Apr 2022 23:44:33 +0300
Subject: more em-sized fonts

---
 src/components/emoji_input/emoji_input.vue | 2 +-
 1 file changed, 1 insertion(+), 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 aa2950ce..7d95ab7e 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -78,7 +78,7 @@
     top: 0;
     right: 0;
     margin: .2em .25em;
-    font-size: 16px;
+    font-size: 1.3em;
     cursor: pointer;
     line-height: 24px;
 
-- 
cgit v1.2.3-70-g09d2


From 0a79a747730bb4a10eb2544412dab68a10602240 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Mon, 29 Aug 2022 18:46:41 -0400
Subject: Use dedicated indicator for non-ascii domain names

---
 src/components/basic_user_card/basic_user_card.js  |  4 ++-
 src/components/basic_user_card/basic_user_card.vue |  8 ++---
 src/components/emoji_input/emoji_input.js          |  4 ++-
 src/components/emoji_input/emoji_input.vue         | 16 ++++++++-
 src/components/emoji_input/suggestor.js            | 11 ++++---
 src/components/mention_link/mention_link.js        |  2 ++
 src/components/mention_link/mention_link.vue       |  3 ++
 src/components/notification/notification.js        |  4 ++-
 src/components/notification/notification.vue       | 21 ++++++------
 src/components/status/status.js                    |  4 ++-
 src/components/status/status.vue                   | 16 ++++-----
 .../unicode_domain_indicator.vue                   | 26 +++++++++++++++
 src/components/user_card/user_card.js              |  4 ++-
 src/components/user_card/user_card.vue             |  9 ++---
 src/components/user_link/user_link.vue             | 38 ++++++++++++++++++++++
 .../user_list_popover/user_list_popover.js         |  2 ++
 .../user_list_popover/user_list_popover.vue        |  2 +-
 .../user_reporting_modal/user_reporting_modal.js   |  4 ++-
 .../user_reporting_modal/user_reporting_modal.vue  | 10 ++++--
 src/i18n/en.json                                   |  3 ++
 .../entity_normalizer/entity_normalizer.service.js |  6 ++--
 .../entity_normalizer/entity_normalizer.spec.js    |  3 +-
 22 files changed, 151 insertions(+), 49 deletions(-)
 create mode 100644 src/components/unicode_domain_indicator/unicode_domain_indicator.vue
 create mode 100644 src/components/user_link/user_link.vue

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

diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js
index 8b1a2c38..31de2d75 100644
--- a/src/components/basic_user_card/basic_user_card.js
+++ b/src/components/basic_user_card/basic_user_card.js
@@ -1,5 +1,6 @@
 import UserPopover from '../user_popover/user_popover.vue'
 import UserAvatar from '../user_avatar/user_avatar.vue'
+import UserLink from '../user_link/user_link.vue'
 import RichContent from 'src/components/rich_content/rich_content.jsx'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 
@@ -10,7 +11,8 @@ const BasicUserCard = {
   components: {
     UserPopover,
     UserAvatar,
-    RichContent
+    RichContent,
+    UserLink
   },
   methods: {
     userProfileLink (user) {
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 9cca7840..418de926 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -30,12 +30,10 @@
         />
       </div>
       <div>
-        <router-link
+        <user-link
           class="basic-user-card-screen-name"
-          :to="userProfileLink(user)"
-        >
-          @{{ user.screen_name_ui }}
-        </router-link>
+          :user="user"
+        />
       </div>
       <slot />
     </div>
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index 5ba3907f..b664d6b3 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -1,5 +1,6 @@
 import Completion from '../../services/completion/completion.js'
 import EmojiPicker from '../emoji_picker/emoji_picker.vue'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
 import { take } from 'lodash'
 import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
 
@@ -120,7 +121,8 @@ const EmojiInput = {
     }
   },
   components: {
-    EmojiPicker
+    EmojiPicker,
+    UnicodeDomainIndicator
   },
   computed: {
     padEmoji () {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 7d95ab7e..81b81913 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -50,7 +50,21 @@
             <span v-else>{{ suggestion.replacement }}</span>
           </span>
           <div class="label">
-            <span class="displayText">{{ suggestion.displayText }}</span>
+            <span
+              v-if="suggestion.user"
+              class="displayText"
+            >
+              {{ suggestion.displayText }}<UnicodeDomainIndicator
+                :user="suggestion.user"
+                :at="false"
+              />
+            </span>
+            <span
+              v-if="!suggestion.user"
+              class="displayText"
+            >
+              {{ suggestion.displayText }}
+            </span>
             <span class="detailText">{{ suggestion.detailText }}</span>
           </div>
         </div>
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index e8efbd1e..0ddb4d68 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -116,11 +116,12 @@ export const suggestUsers = ({ dispatch, state }) => {
 
       return diff + nameAlphabetically + screenNameAlphabetically
       /* eslint-disable camelcase */
-    }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
-      displayText: screen_name_ui,
-      detailText: name,
-      imageUrl: profile_image_url_original,
-      replacement: '@' + screen_name + ' '
+    }).map((user) => ({
+      user,
+      displayText: user.screen_name_ui,
+      detailText: user.name,
+      imageUrl: user.profile_image_url_original,
+      replacement: '@' + user.screen_name + ' '
     }))
     /* eslint-enable camelcase */
 
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js
index 4a74fbe2..6515bd11 100644
--- a/src/components/mention_link/mention_link.js
+++ b/src/components/mention_link/mention_link.js
@@ -2,6 +2,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
 import { mapGetters, mapState } from 'vuex'
 import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
 import UserAvatar from '../user_avatar/user_avatar.vue'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
 import { defineAsyncComponent } from 'vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
@@ -16,6 +17,7 @@ const MentionLink = {
   name: 'MentionLink',
   components: {
     UserAvatar,
+    UnicodeDomainIndicator,
     UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
   },
   props: {
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
index 3af502ef..869a3257 100644
--- a/src/components/mention_link/mention_link.vue
+++ b/src/components/mention_link/mention_link.vue
@@ -47,6 +47,9 @@
             class="serverName"
             :class="{ '-faded': shouldFadeDomain }"
             v-html="'@' + serverName"
+          /><UnicodeDomainIndicator
+            v-if="shouldShowFullUserName"
+            :user="user"
           />
           </span>
           <span
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index f60a1a6a..ddba560e 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -5,6 +5,7 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
 import UserCard from '../user_card/user_card.vue'
 import Timeago from '../timeago/timeago.vue'
 import Report from '../report/report.vue'
+import UserLink from '../user_link/user_link.vue'
 import RichContent from 'src/components/rich_content/rich_content.jsx'
 import UserPopover from '../user_popover/user_popover.vue'
 import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
@@ -50,7 +51,8 @@ const Notification = {
     Status,
     Report,
     RichContent,
-    UserPopover
+    UserPopover,
+    UserLink
   },
   methods: {
     toggleUserExpanded () {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 6a5417b3..26b174ff 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -11,9 +11,10 @@
       class="Notification container -muted"
     >
       <small>
-        <router-link :to="userProfileLink">
-          {{ notification.from_profile.screen_name_ui }}
-        </router-link>
+        <user-link
+          :user="notification.from_profile"
+          :at="false"
+        />
       </small>
       <button
         class="button-unstyled unmute"
@@ -174,12 +175,10 @@
           v-if="notification.type === 'follow' || notification.type === 'follow_request'"
           class="follow-text"
         >
-          <router-link
-            :to="userProfileLink"
+          <user-link
             class="follow-name"
-          >
-            @{{ notification.from_profile.screen_name_ui }}
-          </router-link>
+            :user="notification.from_profile"
+          />
           <div
             v-if="notification.type === 'follow_request'"
             style="white-space: nowrap;"
@@ -210,9 +209,9 @@
           v-else-if="notification.type === 'move'"
           class="move-text"
         >
-          <router-link :to="targetUserProfileLink">
-            @{{ notification.target.screen_name_ui }}
-          </router-link>
+          <user-link
+            :user="notification.target"
+          />
         </div>
         <Report
           v-else-if="notification.type === 'pleroma:report'"
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 384063a7..19356664 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -13,6 +13,7 @@ import StatusPopover from '../status_popover/status_popover.vue'
 import UserPopover from '../user_popover/user_popover.vue'
 import UserListPopover from '../user_list_popover/user_list_popover.vue'
 import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
+import UserLink from '../user_link/user_link.vue'
 import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
 import MentionLink from 'src/components/mention_link/mention_link.vue'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -115,7 +116,8 @@ const Status = {
     RichContent,
     MentionLink,
     MentionsLine,
-    UserPopover
+    UserPopover,
+    UserLink
   },
   props: [
     'statusoid',
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 967a966c..91f5fffa 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -25,9 +25,10 @@
             class="fa-scale-110 fa-old-padding repeat-icon"
             icon="retweet"
           />
-          <router-link :to="userProfileLink">
-            {{ status.user.screen_name_ui }}
-          </router-link>
+          <user-link
+            :user="status.user"
+            :at="false"
+          />
         </small>
         <small
           v-if="showReasonMutedThread"
@@ -164,13 +165,12 @@
                 >
                   {{ status.user.name }}
                 </h4>
-                <router-link
+                <user-link
                   class="account-name"
                   :title="status.user.screen_name_ui"
-                  :to="userProfileLink"
-                >
-                  {{ status.user.screen_name_ui }}
-                </router-link>
+                  :user="status.user"
+                  :at="false"
+                />
                 <img
                   v-if="!!(status.user && status.user.favicon)"
                   class="status-favicon"
diff --git a/src/components/unicode_domain_indicator/unicode_domain_indicator.vue b/src/components/unicode_domain_indicator/unicode_domain_indicator.vue
new file mode 100644
index 00000000..8f35245f
--- /dev/null
+++ b/src/components/unicode_domain_indicator/unicode_domain_indicator.vue
@@ -0,0 +1,26 @@
+<template>
+  <FAIcon
+    v-if="user && user.screen_name_ui_contains_non_ascii"
+    icon="code"
+    :title="$t('unicode_domain_indicator.tooltip')"
+  />
+</template>
+
+<script>
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+  faCode
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+  faCode
+)
+
+const UnicodeDomainIndicator = {
+  props: {
+    user: Object
+  }
+}
+
+export default UnicodeDomainIndicator
+</script>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 4c81e2c7..37458ba2 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -5,6 +5,7 @@ import FollowButton from '../follow_button/follow_button.vue'
 import ModerationTools from '../moderation_tools/moderation_tools.vue'
 import AccountActions from '../account_actions/account_actions.vue'
 import Select from '../select/select.vue'
+import UserLink from '../user_link/user_link.vue'
 import RichContent from 'src/components/rich_content/rich_content.jsx'
 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 import { mapGetters } from 'vuex'
@@ -134,7 +135,8 @@ export default {
     ProgressButton,
     FollowButton,
     Select,
-    RichContent
+    RichContent,
+    UserLink
   },
   methods: {
     muteUser () {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index ace89c51..b84ff27d 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -106,13 +106,10 @@
               </button>
             </div>
             <div class="bottom-line">
-              <router-link
+              <user-link
                 class="user-screen-name"
-                :title="user.screen_name_ui"
-                :to="userProfileLink(user)"
-              >
-                @{{ user.screen_name_ui }}
-              </router-link>
+                :user="user"
+              />
               <template v-if="!hideBio">
                 <span
                   v-if="user.deactivated"
diff --git a/src/components/user_link/user_link.vue b/src/components/user_link/user_link.vue
new file mode 100644
index 00000000..efd96e12
--- /dev/null
+++ b/src/components/user_link/user_link.vue
@@ -0,0 +1,38 @@
+<template>
+  <router-link
+    :title="user.screen_name_ui"
+    :to="userProfileLink(user)"
+  >
+    {{ at ? '@' : '' }}{{ user.screen_name_ui }}<UnicodeDomainIndicator
+      :user="user"
+    />
+  </router-link>
+</template>
+
+<script>
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+
+const UserLink = {
+  props: {
+    user: Object,
+    at: {
+      type: Boolean,
+      default: true
+    }
+  },
+  components: {
+    UnicodeDomainIndicator
+  },
+  methods: {
+    userProfileLink (user) {
+      return generateProfileLink(
+        user.id, user.screen_name,
+        this.$store.state.instance.restrictedNicknames
+      )
+    }
+  }
+}
+
+export default UserLink
+</script>
diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js
index e24eb9f7..046e0abd 100644
--- a/src/components/user_list_popover/user_list_popover.js
+++ b/src/components/user_list_popover/user_list_popover.js
@@ -1,5 +1,6 @@
 import { defineAsyncComponent } from 'vue'
 import RichContent from 'src/components/rich_content/rich_content.jsx'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
 
 import { library } from '@fortawesome/fontawesome-svg-core'
 import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@@ -15,6 +16,7 @@ const UserListPopover = {
   ],
   components: {
     RichContent,
+    UnicodeDomainIndicator,
     Popover: defineAsyncComponent(() => import('../popover/popover.vue')),
     UserAvatar: defineAsyncComponent(() => import('../user_avatar/user_avatar.vue'))
   },
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
index a3ce54c3..635dc7f6 100644
--- a/src/components/user_list_popover/user_list_popover.vue
+++ b/src/components/user_list_popover/user_list_popover.vue
@@ -29,7 +29,7 @@
                 :emoji="user.emoji"
               />
               <!-- eslint-enable vue/no-v-html -->
-              <span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
+              <span class="user-list-screen-name">{{ user.screen_name_ui }}</span><UnicodeDomainIndicator :user="user" />
             </div>
           </div>
         </template>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index 85ffc661..67fde084 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.js
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -2,13 +2,15 @@ import Status from '../status/status.vue'
 import List from '../list/list.vue'
 import Checkbox from '../checkbox/checkbox.vue'
 import Modal from '../modal/modal.vue'
+import UserLink from '../user_link/user_link.vue'
 
 const UserReportingModal = {
   components: {
     Status,
     List,
     Checkbox,
-    Modal
+    Modal,
+    UserLink
   },
   data () {
     return {
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
index 429a66e2..8c42ab7b 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.vue
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -5,9 +5,13 @@
   >
     <div class="user-reporting-panel panel">
       <div class="panel-heading">
-        <div class="title">
-          {{ $t('user_reporting.title', [user.screen_name_ui]) }}
-        </div>
+        <i18n-t
+          tag="div"
+          keypath="user_reporting.title"
+          class="title"
+        >
+          <UserLink :user="user" />
+        </i18n-t>
       </div>
       <div class="panel-body">
         <div class="user-reporting-panel-left">
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 91722d9a..4ce56678 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1006,5 +1006,8 @@
     "update_changelog": "For more details on what's changed, see {theFullChangelog}.",
     "update_changelog_here": "the full changelog",
     "art_by": "Art by {linkToArtist}"
+  },
+  "unicode_domain_indicator": {
+    "tooltip": "This domain contains non-ascii characters."
   }
 }
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index e9cbcfe6..ce316832 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -214,12 +214,14 @@ export const parseUser = (data) => {
   output.screen_name_ui = output.screen_name
   if (output.screen_name && output.screen_name.includes('@')) {
     const parts = output.screen_name.split('@')
-    let unicodeDomain = punycode.toUnicode(parts[1])
+    const unicodeDomain = punycode.toUnicode(parts[1])
     if (unicodeDomain !== parts[1]) {
       // Add some identifier so users can potentially spot spoofing attempts:
       // lain.com and xn--lin-6cd.com would appear identical otherwise.
-      unicodeDomain = '🌏' + unicodeDomain
+      output.screen_name_ui_contains_non_ascii = true
       output.screen_name_ui = [parts[0], unicodeDomain].join('@')
+    } else {
+      output.screen_name_ui_contains_non_ascii = false
     }
   }
 
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
index 98bb05a8..3923596b 100644
--- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
+++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -269,7 +269,8 @@ describe('API Entities normalizer', () => {
     it('converts IDN to unicode and marks it as internatonal', () => {
       const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' })
 
-      expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com')
+      expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@lаin.com')
+      expect(parseUser(user)).to.have.property('screen_name_ui_contains_non_ascii').that.equal(true)
     })
   })
 
-- 
cgit v1.2.3-70-g09d2


From 06a636db3732ce2808c54d3b74eb4aabd866dbf6 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Fri, 8 Oct 2021 15:09:24 -0400
Subject: Lazy-load emoji picker in post form

When clicking the reply button, we used to load the whole emoji picker.
This causes a considerable delay even if the user is not going to use
the emoji picker. Now the content of the emoji picker is loaded only
after the user has explicitly opened the emoji picker.

Ref: grouped-emoji-picker
---
 src/components/emoji_input/emoji_input.vue   |  1 +
 src/components/emoji_picker/emoji_picker.js  | 24 +++++++++++++++++++-----
 src/components/emoji_picker/emoji_picker.vue |  9 +++++++--
 3 files changed, 27 insertions(+), 7 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 81b81913..eedde9aa 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -19,6 +19,7 @@
         v-if="enableEmojiPicker"
         ref="picker"
         :class="{ hide: !showPicker }"
+        :showing="showPicker"
         :enable-sticker-picker="enableStickerPicker"
         class="emoji-picker-panel"
         @emoji="insert"
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index 5b90c31e..8b4f302f 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -39,6 +39,10 @@ const EmojiPicker = {
       required: false,
       type: Boolean,
       default: false
+    },
+    showing: {
+      required: true,
+      type: Boolean
     }
   },
   data () {
@@ -48,7 +52,9 @@ const EmojiPicker = {
       showingStickers: false,
       groupsScrolledClass: 'scrolled-top',
       keepOpen: false,
-      customEmojiTimeout: null
+      customEmojiTimeout: null,
+      // Lazy-load only after the first time `showing` becomes true.
+      contentLoaded: false
     }
   },
   components: {
@@ -115,6 +121,9 @@ const EmojiPicker = {
       this.$lozad = lozad('img', {})
       this.$lozad.observe()
     },
+    waitForDomAndInitializeLazyLoad() {
+      this.$nextTick(() => this.initializeLazyLoad())
+    },
     destroyLazyLoad () {
       if (this.$lozad) {
         if (this.$lozad.observer) {
@@ -129,18 +138,23 @@ const EmojiPicker = {
   watch: {
     keyword () {
       this.onScroll()
-      // Wait for the dom to change
-      this.$nextTick(() => this.initializeLazyLoad())
+      this.waitForDomAndInitializeLazyLoad()
     },
     allCustomGroups () {
-      this.$nextTick(() => this.initializeLazyLoad())
+      this.waitForDomAndInitializeLazyLoad()
+    },
+    showing (val) {
+      if (val) {
+        this.contentLoaded = true
+        this.waitForDomAndInitializeLazyLoad()
+      }
     }
   },
   mounted () {
     if (this.defaultGroup) {
       this.highlight(this.defaultGroup)
     }
-    this.initializeLazyLoad()
+    this.waitForDomAndInitializeLazyLoad()
   },
   destroyed () {
     this.destroyLazyLoad()
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index ed196066..b92bccd7 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,5 +1,7 @@
 <template>
-  <div class="emoji-picker panel panel-default panel-body">
+  <div
+    class="emoji-picker panel panel-default panel-body"
+  >
     <div class="heading">
       <span class="emoji-tabs">
         <span
@@ -45,7 +47,10 @@
         </span>
       </span>
     </div>
-    <div class="content">
+    <div
+      v-if="contentLoaded"
+      class="content"
+    >
       <div
         class="emoji-content"
         :class="{hidden: showingStickers}"
-- 
cgit v1.2.3-70-g09d2


From a7f836a64e334d6c46ed1e12c7bf9b2ff4a09c7e Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Wed, 21 Sep 2022 23:16:33 -0400
Subject: Make suggestor suggest according to cldr annotations

---
 src/components/emoji_input/emoji_input.js  | 49 ++++++++++++++++++++++++++++--
 src/components/emoji_input/emoji_input.vue |  2 +-
 src/components/emoji_input/suggestor.js    | 32 +++++++++----------
 3 files changed, 64 insertions(+), 19 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 fb2096c9..ffc0ffac 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -3,7 +3,7 @@ import EmojiPicker from '../emoji_picker/emoji_picker.vue'
 import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
 import { take } from 'lodash'
 import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
-
+import { ensureFinalFallback } from '../../i18n/languages.js'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faSmileBeam
@@ -143,6 +143,51 @@ const EmojiInput = {
         const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
         return word
       }
+    },
+    languages () {
+      return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
+    },
+    maybeLocalizedEmojiNamesAndKeywords () {
+      return 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 () {
+      return 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
+      }
     }
   },
   mounted () {
@@ -181,7 +226,7 @@ const EmojiInput = {
       const firstchar = newWord.charAt(0)
       this.suggestions = []
       if (newWord === firstchar) return
-      const matchedSuggestions = await this.suggest(newWord)
+      const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
       // Async: cancel if textAtCaret has changed during wait
       if (this.textAtCaret !== newWord) return
       if (matchedSuggestions.length <= 0) return
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index eedde9aa..43581dbf 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -64,7 +64,7 @@
               v-if="!suggestion.user"
               class="displayText"
             >
-              {{ suggestion.displayText }}
+              {{ maybeLocalizedEmojiName(suggestion) }}
             </span>
             <span class="detailText">{{ suggestion.detailText }}</span>
           </div>
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index 1765f843..adaa879e 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -13,10 +13,10 @@
 export default data => {
   const emojiCurry = suggestEmoji(data.emoji)
   const usersCurry = data.store && suggestUsers(data.store)
-  return input => {
+  return (input, nameKeywordLocalizer) => {
     const firstChar = input[0]
     if (firstChar === ':' && data.emoji) {
-      return emojiCurry(input)
+      return emojiCurry(input, nameKeywordLocalizer)
     }
     if (firstChar === '@' && usersCurry) {
       return usersCurry(input)
@@ -25,34 +25,34 @@ export default data => {
   }
 }
 
-export const suggestEmoji = emojis => input => {
+export const suggestEmoji = emojis => (input, nameKeywordLocalizer) => {
   const noPrefix = input.toLowerCase().substr(1)
   return emojis
-    .filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
-    .sort((a, b) => {
-      let aScore = 0
-      let bScore = 0
+    .map(emoji => ({ ...emoji, ...nameKeywordLocalizer(emoji) }))
+    .filter((emoji) => (emoji.names.concat(emoji.keywords)).filter(kw => kw.toLowerCase().match(noPrefix)).length)
+    .map(k => {
+      let score = 0
 
       // An exact match always wins
-      aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
-      bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
+      score += Math.max(...k.names.map(name => name.toLowerCase() === noPrefix ? 200 : 0), 0)
 
       // Prioritize custom emoji a lot
-      aScore += a.imageUrl ? 100 : 0
-      bScore += b.imageUrl ? 100 : 0
+      score += k.imageUrl ? 100 : 0
 
       // Prioritize prefix matches somewhat
-      aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
-      bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
+      score += Math.max(...k.names.map(kw => kw.toLowerCase().startsWith(noPrefix) ? 10 : 0), 0)
 
       // Sort by length
-      aScore -= a.displayText.length
-      bScore -= b.displayText.length
+      score -= k.displayText.length
 
+      k.score = score
+      return k
+    })
+    .sort((a, b) => {
       // Break ties alphabetically
       const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
 
-      return bScore - aScore + alphabetically
+      return b.score - a.score + alphabetically
     })
 }
 
-- 
cgit v1.2.3-70-g09d2


From 4631b1b9f7e7813cab65e0d0705cd1e265f23393 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 9 Oct 2022 22:09:50 +0300
Subject: suggestor popover

---
 src/components/emoji_input/emoji_input.js  |  80 +++++++++++-------
 src/components/emoji_input/emoji_input.vue | 128 +++++++++++++++--------------
 src/components/popover/popover.js          |  11 ++-
 src/components/popover/popover.vue         |   2 +-
 4 files changed, 127 insertions(+), 94 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 ffc0ffac..b365043c 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -1,5 +1,6 @@
 import Completion from '../../services/completion/completion.js'
 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 { take } from 'lodash'
 import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@@ -109,6 +110,7 @@ const EmojiInput = {
   data () {
     return {
       input: undefined,
+      caretEl: undefined,
       highlighted: 0,
       caret: 0,
       focused: false,
@@ -117,10 +119,12 @@ const EmojiInput = {
       temporarilyHideSuggestions: false,
       keepOpen: false,
       disableClickOutside: false,
-      suggestions: []
+      suggestions: [],
+      overlayStyle: {}
     }
   },
   components: {
+    Popover,
     EmojiPicker,
     UnicodeDomainIndicator
   },
@@ -128,7 +132,15 @@ const EmojiInput = {
     padEmoji () {
       return this.$store.getters.mergedConfig.padEmoji
     },
+    preText () {
+      return this.modelValue.slice(0, this.caret)
+    },
+    postText () {
+      return this.modelValue.slice(this.caret)
+    },
     showSuggestions () {
+      console.log(this.focused)
+      console.log(this.suggestions)
       return this.focused &&
         this.suggestions &&
         this.suggestions.length > 0 &&
@@ -191,10 +203,21 @@ const EmojiInput = {
     }
   },
   mounted () {
-    const { root } = this.$refs
+    const { root, hiddenOverlayCaret, suggestorPopover } = this.$refs
     const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
     if (!input) return
     this.input = input
+    this.caretEl = hiddenOverlayCaret
+    suggestorPopover.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()
     input.addEventListener('blur', this.onBlur)
     input.addEventListener('focus', this.onFocus)
@@ -204,6 +227,16 @@ const EmojiInput = {
     input.addEventListener('click', this.onClickInput)
     input.addEventListener('transitionend', this.onTransition)
     input.addEventListener('input', this.onInput)
+    input.addEventListener('scroll', (e) => {
+      console.log({
+        top: this.input.scrollTop,
+        left: this.input.scrollLeft
+      })
+      this.$refs.hiddenOverlay.scrollTo({
+        top: this.input.scrollTop,
+        left: this.input.scrollLeft
+      })
+    })
   },
   unmounted () {
     const { input } = this
@@ -219,22 +252,32 @@ const EmojiInput = {
     }
   },
   watch: {
-    showSuggestions: function (newValue) {
+    showSuggestions: function (newValue, oldValue) {
       this.$emit('shown', newValue)
+      if (newValue) {
+        this.$refs.suggestorPopover.showPopover()
+      } else {
+        this.$refs.suggestorPopover.hidePopover()
+      }
     },
     textAtCaret: async function (newWord) {
       const firstchar = newWord.charAt(0)
-      this.suggestions = []
-      if (newWord === firstchar) return
+      if (newWord === firstchar) {
+        this.suggestions = []
+        return
+      }
       const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
       // Async: cancel if textAtCaret has changed during wait
-      if (this.textAtCaret !== newWord) return
-      if (matchedSuggestions.length <= 0) return
+      if (this.textAtCaret !== newWord || matchedSuggestions.length <= 0) {
+        this.suggestions = []
+        return
+      }
       this.suggestions = take(matchedSuggestions, 5)
         .map(({ imageUrl, ...rest }) => ({
           ...rest,
           img: imageUrl || ''
         }))
+      this.$refs.suggestorPopover.updateStyles()
     },
     suggestions: {
       handler (newValue) {
@@ -525,29 +568,6 @@ const EmojiInput = {
       this.caret = selectionStart
     },
     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
     }
   }
 }
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 43581dbf..8a4bc244 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -6,6 +6,12 @@
     :class="{ 'with-picker': !hideEmojiButton }"
   >
     <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">
       <button
         v-if="!hideEmojiButton"
@@ -27,50 +33,52 @@
         @sticker-upload-failed="onStickerUploadFailed"
       />
     </template>
-    <div
-      ref="panel"
+    <Popover
       class="autocomplete-panel"
-      :class="{ hide: !showSuggestions }"
+      placement="bottom"
+      ref="suggestorPopover"
     >
-      <div
-        ref="panel-body"
-        class="autocomplete-panel-body"
-      >
+      <template #content>
         <div
-          v-for="(suggestion, index) in suggestions"
-          :key="index"
-          class="autocomplete-item"
-          :class="{ highlighted: index === highlighted }"
-          @click.stop.prevent="onClick($event, suggestion)"
+          ref="panel-body"
+          class="autocomplete-panel-body"
         >
-          <span class="image">
-            <img
-              v-if="suggestion.img"
-              :src="suggestion.img"
-            >
-            <span v-else>{{ suggestion.replacement }}</span>
-          </span>
-          <div class="label">
-            <span
-              v-if="suggestion.user"
-              class="displayText"
-            >
-              {{ suggestion.displayText }}<UnicodeDomainIndicator
-                :user="suggestion.user"
-                :at="false"
-              />
+          <div
+            v-for="(suggestion, index) in suggestions"
+            :key="index"
+            class="autocomplete-item"
+            :class="{ highlighted: index === 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>
-            <span
-              v-if="!suggestion.user"
-              class="displayText"
-            >
-              {{ maybeLocalizedEmojiName(suggestion) }}
-            </span>
-            <span class="detailText">{{ suggestion.detailText }}</span>
+            <div class="label">
+              <span
+                v-if="suggestion.user"
+                class="displayText"
+              >
+                {{ suggestion.displayText }}<UnicodeDomainIndicator
+                  :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>
+      </template>
+    </Popover>
   </div>
 </template>
 
@@ -102,6 +110,7 @@
       color: var(--text, $fallback--text);
     }
   }
+
   .emoji-picker-panel {
     position: absolute;
     z-index: 20;
@@ -115,31 +124,6 @@
   .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 {
@@ -196,5 +180,25 @@
   input, textarea {
     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;
+    }
+  }
 }
 </style>
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index dd332c35..60c42f0f 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -51,6 +51,10 @@ const Popover = {
       // 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
       // 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,
       hidden: true,
       styles: {},
@@ -63,6 +67,10 @@ const Popover = {
     }
   },
   methods: {
+    setAnchorEl (el) {
+      this.anchorEl = el
+      this.updateStyles()
+    },
     containerBoundingClientRect () {
       const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
       return container.getBoundingClientRect()
@@ -75,7 +83,7 @@ const Popover = {
 
       // 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.
-      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
       const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
       const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
@@ -319,6 +327,7 @@ const Popover = {
     }
   },
   mounted () {
+    this.teleport = true
     let scrollable = this.$refs.trigger.closest('.column.-scrollable') ||
         this.$refs.trigger.closest('.mobile-notifications')
     if (!scrollable) scrollable = window
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
index 623af8d2..217eba7c 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -11,7 +11,7 @@
     >
       <slot name="trigger" />
     </button>
-    <teleport to="#popovers">
+    <teleport :disabled="!teleport" to="#popovers">
       <transition name="fade">
         <div
           v-if="!hidden"
-- 
cgit v1.2.3-70-g09d2


From 2469415809c7bff4dac97c469fd4aa74f9191a75 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 9 Oct 2022 23:02:42 +0300
Subject: fix css

---
 src/components/emoji_input/emoji_input.vue | 111 ++++++++++++++---------------
 1 file changed, 55 insertions(+), 56 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 8a4bc244..68498ef0 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -121,62 +121,6 @@
     }
   }
 
-  .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);
-      }
-    }
-  }
-
   input, textarea {
     flex: 1 0 auto;
   }
@@ -201,4 +145,59 @@
     }
   }
 }
+.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>
-- 
cgit v1.2.3-70-g09d2


From 296a6fa4e3d14f0c8ca091870fd4f7e85bc34df8 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 9 Oct 2022 23:42:36 +0300
Subject: some shitty initial implementation of emoji picker with popover

---
 src/components/emoji_input/emoji_input.js     |  36 ++---
 src/components/emoji_input/emoji_input.vue    |   5 +-
 src/components/emoji_picker/emoji_picker.js   |  32 ++--
 src/components/emoji_picker/emoji_picker.scss |   8 +-
 src/components/emoji_picker/emoji_picker.vue  | 210 +++++++++++++-------------
 src/components/popover/popover.js             |   6 +
 6 files changed, 150 insertions(+), 147 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 5131e699..635251be 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -115,12 +115,12 @@ const EmojiInput = {
       caret: 0,
       focused: false,
       blurTimeout: null,
-      showPicker: false,
       temporarilyHideSuggestions: false,
       keepOpen: false,
       disableClickOutside: false,
       suggestions: [],
-      overlayStyle: {}
+      overlayStyle: {},
+      pickerShown: false
     }
   },
   components: {
@@ -142,7 +142,6 @@ const EmojiInput = {
       return this.focused &&
         this.suggestions &&
         this.suggestions.length > 0 &&
-        !this.showPicker &&
         !this.temporarilyHideSuggestions
     },
     textAtCaret () {
@@ -285,8 +284,8 @@ const EmojiInput = {
       if (pickerInput) pickerInput.focus()
     },
     triggerShowPicker () {
-      this.showPicker = true
       this.$nextTick(() => {
+        this.$refs.picker.showPicker()
         this.scrollIntoView()
         this.focusPickerInput()
       })
@@ -299,12 +298,16 @@ const EmojiInput = {
       }, 0)
     },
     togglePicker () {
+      console.log('piick')
       this.input.focus()
-      this.showPicker = !this.showPicker
-      if (this.showPicker) {
+      if (!this.pickerShown) {
+        console.log('pick')
         this.scrollIntoView()
+        this.$refs.picker.showPicker()
         this.$refs.picker.startEmojiLoad()
         this.$nextTick(this.focusPickerInput)
+      } else {
+        this.$refs.picker.hidePicker()
       }
     },
     replace (replacement) {
@@ -441,6 +444,12 @@ const EmojiInput = {
         }
       })
     },
+    onPickerShown () {
+      this.pickerShown = true
+    },
+    onPickerClosed () {
+      this.pickerShown = false
+    },
     onBlur (e) {
       // Clicking on any suggestion removes focus from autocomplete,
       // preventing click handler ever executing.
@@ -458,9 +467,6 @@ const EmojiInput = {
         this.blurTimeout = null
       }
 
-      if (!this.keepOpen) {
-        this.showPicker = false
-      }
       this.focused = true
       this.setCaret(e)
       this.temporarilyHideSuggestions = false
@@ -523,27 +529,15 @@ const EmojiInput = {
           this.input.focus()
         }
       }
-
-      this.showPicker = false
     },
     onInput (e) {
-      this.showPicker = false
       this.setCaret(e)
       this.$emit('update:modelValue', e.target.value)
     },
-    onClickInput (e) {
-      this.showPicker = false
-    },
-    onClickOutside (e) {
-      if (this.disableClickOutside) return
-      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 } }) {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 68498ef0..63bf856e 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -1,7 +1,6 @@
 <template>
   <div
     ref="root"
-    v-click-outside="onClickOutside"
     class="emoji-input"
     :class="{ 'with-picker': !hideEmojiButton }"
   >
@@ -24,13 +23,13 @@
       <EmojiPicker
         v-if="enableEmojiPicker"
         ref="picker"
-        :class="{ hide: !showPicker }"
-        :showing="showPicker"
         :enable-sticker-picker="enableStickerPicker"
         class="emoji-picker-panel"
         @emoji="insert"
         @sticker-uploaded="onStickerUploaded"
         @sticker-upload-failed="onStickerUploadFailed"
+        @show="onPickerShown"
+        @close="onPickerClosed"
       />
     </template>
     <Popover
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index fafc2af1..d70abf5b 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -1,5 +1,6 @@
 import { defineAsyncComponent } from 'vue'
 import Checkbox from '../checkbox/checkbox.vue'
+import Popover from 'src/components/popover/popover.vue'
 import StillImage from '../still-image/still-image.vue'
 import { ensureFinalFallback } from '../../i18n/languages.js'
 import lozad from 'lozad'
@@ -87,10 +88,6 @@ const EmojiPicker = {
       required: false,
       type: Boolean,
       default: false
-    },
-    showing: {
-      required: true,
-      type: Boolean
     }
   },
   data () {
@@ -111,15 +108,30 @@ const EmojiPicker = {
   components: {
     StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
     Checkbox,
-    StillImage
+    StillImage,
+    Popover
   },
   methods: {
+    showPicker () {
+      console.log('pick')
+      this.$refs.popover.showPopover()
+      this.onShowing()
+    },
+    hidePicker () {
+      this.$refs.popover.hidePopover()
+    },
     setGroupRef (name) {
       return el => { this.groupRefs[name] = el }
     },
     setEmojiRef (name) {
       return el => { this.emojiRefs[name] = el }
     },
+    onPopoverShown () {
+      this.$emit('show')
+    },
+    onPopoverClosed () {
+      this.$emit('close')
+    },
     onStickerUploaded (e) {
       this.$emit('sticker-uploaded', e)
     },
@@ -251,16 +263,6 @@ const EmojiPicker = {
     allCustomGroups () {
       this.waitForDomAndInitializeLazyLoad()
       this.filteredEmojiGroups = this.getFilteredEmojiGroups()
-    },
-    showing (val) {
-      if (val) {
-        this.onShowing()
-      }
-    }
-  },
-  mounted () {
-    if (this.showing) {
-      this.onShowing()
     }
   },
   destroyed () {
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 016c46d7..53363ec1 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -6,14 +6,10 @@ $emoji-picker-header-picture-height: 32px;
 $emoji-picker-emoji-size: 32px;
 
 .emoji-picker {
+  width: 25em;
+  max-width: 100vw;
   display: flex;
   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: var(--popover, $fallback--bg);
   color: $fallback--link;
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index 57bb0037..ccd45bc1 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,129 +1,135 @@
 <template>
-  <div
-    class="emoji-picker panel panel-default panel-body"
+  <Popover
+    trigger="click"
+    popover-class="emoji-picker popover-default"
+    ref="popover"
+    @show="onPopoverShown"
+    @close="onPopoverClosed"
   >
-    <div class="heading">
-      <span
-        ref="header"
-        class="emoji-tabs"
-      >
+    <template #content>
+      <div class="heading">
         <span
-          v-for="group in filteredEmojiGroups"
-          :ref="setGroupRef('group-header-' + group.id)"
-          :key="group.id"
-          class="emoji-tabs-item"
-          :class="{
-            active: activeGroupView === group.id
-          }"
-          :title="group.text"
-          @click.prevent="highlight(group.id)"
+          ref="header"
+          class="emoji-tabs"
         >
           <span
-            v-if="group.image"
-            class="emoji-picker-header-image"
+            v-for="group in filteredEmojiGroups"
+            :ref="setGroupRef('group-header-' + group.id)"
+            :key="group.id"
+            class="emoji-tabs-item"
+            :class="{
+              active: activeGroupView === group.id
+            }"
+            :title="group.text"
+            @click.prevent="highlight(group.id)"
           >
-            <still-image
-              :alt="group.text"
-              :src="group.image"
+            <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>
-          <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"
+          v-if="stickerPickerEnabled"
+          class="additional-tabs"
         >
-          <FAIcon
-            icon="sticky-note"
-            fixed-width
-          />
+          <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>
-      </span>
-    </div>
-    <div
-      v-if="contentLoaded"
-      class="content"
-    >
+      </div>
       <div
-        class="emoji-content"
-        :class="{hidden: showingStickers}"
+        v-if="contentLoaded"
+        class="content"
       >
-        <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"
+          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
-            v-for="group in filteredEmojiGroups"
-            :key="group.id"
-            class="emoji-group"
+            ref="emoji-groups"
+            class="emoji-groups"
+            :class="groupsScrolledClass"
+            @scroll="onScroll"
           >
-            <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)"
+            <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-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)" />
+                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 class="keep-open">
-          <Checkbox v-model="keepOpen">
-            {{ $t('emoji.keep_open') }}
-          </Checkbox>
+        <div
+          v-if="showingStickers"
+          class="stickers-content"
+        >
+          <sticker-picker
+            @uploaded="onStickerUploaded"
+            @upload-failed="onStickerUploadFailed"
+          />
         </div>
       </div>
-      <div
-        v-if="showingStickers"
-        class="stickers-content"
-      >
-        <sticker-picker
-          @uploaded="onStickerUploaded"
-          @upload-failed="onStickerUploadFailed"
-        />
-      </div>
-    </div>
-  </div>
+    </template>
+  </Popover>
 </template>
 
 <script src="./emoji_picker.js"></script>
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index 60c42f0f..3474d354 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -63,6 +63,7 @@ const Popover = {
       // used to avoid blinking if hovered onto popover
       graceTimeout: null,
       parentPopover: null,
+      disableClickOutside: false,
       childrenShown: new Set()
     }
   },
@@ -234,6 +235,10 @@ const Popover = {
     },
     showPopover () {
       if (this.disabled) return
+      this.disableClickOutside = true
+      setTimeout(() => {
+        this.disableClickOutside = false
+      }, 0)
       const wasHidden = this.hidden
       this.hidden = false
       this.parentPopover && this.parentPopover.onChildPopoverState(this, true)
@@ -294,6 +299,7 @@ const Popover = {
       }
     },
     onClickOutside (e) {
+      if (this.disableClickOutside) return
       if (this.hidden) return
       if (this.$refs.content && this.$refs.content.contains(e.target)) return
       if (this.$el.contains(e.target)) return
-- 
cgit v1.2.3-70-g09d2


From 3c0e7882b10989424c3ec98413b555373efa1719 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 21 Nov 2022 22:17:33 +0200
Subject: lint

---
 src/components/emoji_input/emoji_input.vue                 | 13 ++++++++++---
 src/components/emoji_picker/emoji_picker.vue               |  4 ++--
 src/components/mobile_nav/mobile_nav.vue                   |  6 +++---
 src/components/notifications/notifications.vue             |  2 +-
 src/components/popover/popover.vue                         |  5 ++++-
 .../quick_filter_settings/quick_filter_settings.vue        |  2 +-
 src/components/quick_view_settings/quick_view_settings.vue |  2 +-
 src/components/search/search.js                            |  4 ++--
 src/components/timeline/timeline.vue                       | 14 ++++++++++----
 9 files changed, 34 insertions(+), 18 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 63bf856e..c9bbc18f 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -6,9 +6,16 @@
   >
     <slot />
     <!-- TODO: make the 'x' disappear if at the end maybe? -->
-    <div class="hidden-overlay" :style="overlayStyle" ref="hiddenOverlay">
+    <div
+      ref="hiddenOverlay"
+      class="hidden-overlay"
+      :style="overlayStyle"
+    >
       <span>{{ preText }}</span>
-      <span class="caret" ref="hiddenOverlayCaret">x</span>
+      <span
+        ref="hiddenOverlayCaret"
+        class="caret"
+      >x</span>
       <span>{{ postText }}</span>
     </div>
     <template v-if="enableEmojiPicker">
@@ -33,9 +40,9 @@
       />
     </template>
     <Popover
+      ref="suggestorPopover"
       class="autocomplete-panel"
       placement="bottom"
-      ref="suggestorPopover"
     >
       <template #content>
         <div
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index a2c99c16..ff56d637 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,8 +1,8 @@
 <template>
   <Popover
+    ref="popover"
     trigger="click"
     popover-class="emoji-picker popover-default"
-    ref="popover"
     @show="onPopoverShown"
     @close="onPopoverClosed"
   >
@@ -66,12 +66,12 @@
         >
           <div class="emoji-search">
             <input
+              ref="search"
               v-model="keyword"
               type="text"
               class="form-control"
               :placeholder="$t('emoji.search_emoji')"
               @input="$event.target.composing = false"
-              ref="search"
             >
           </div>
           <div
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 6e732d1f..d642008b 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -11,7 +11,7 @@
         <button
           class="button-unstyled mobile-nav-button"
           :title="$t('nav.mobile_sidebar')"
-          :aria-expanaded="this.$refs.sideDrawer && !this.$refs.sideDrawer.closed"
+          :aria-expanaded="$refs.sideDrawer && !$refs.sideDrawer.closed"
           @click.stop.prevent="toggleMobileSidebar()"
         >
           <FAIcon
@@ -51,7 +51,7 @@
     >
       <div class="mobile-notifications-header">
         <span class="title">{{ $t('notifications.notifications') }}</span>
-        <span class="spacer"/>
+        <span class="spacer" />
         <button
           v-if="notificationsAtTop"
           class="button-unstyled mobile-nav-button"
@@ -79,8 +79,8 @@
       </div>
       <div
         id="mobile-notifications"
-        class="mobile-notifications"
         ref="mobileNotifications"
+        class="mobile-notifications"
         @scroll="onScroll"
       />
     </aside>
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 3d5878d4..633efca6 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -22,8 +22,8 @@
             >{{ unseenCount }}</span>
           </div>
           <div
-            class="rightside-button"
             v-if="showScrollTop"
+            class="rightside-button"
           >
             <button
               class="button-unstyled scroll-to-top-button"
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
index c2cf2327..2869d736 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -12,7 +12,10 @@
     >
       <slot name="trigger" />
     </button>
-    <teleport :disabled="!teleport" to="#popovers">
+    <teleport
+      :disabled="!teleport"
+      to="#popovers"
+    >
       <transition name="fade">
         <div
           v-if="!hidden"
diff --git a/src/components/quick_filter_settings/quick_filter_settings.vue b/src/components/quick_filter_settings/quick_filter_settings.vue
index 87fcd716..f2aa61ee 100644
--- a/src/components/quick_filter_settings/quick_filter_settings.vue
+++ b/src/components/quick_filter_settings/quick_filter_settings.vue
@@ -3,7 +3,7 @@
     trigger="click"
     class="QuickFilterSettings"
     :bound-to="{ x: 'container' }"
-    :triggerAttrs="{ title: $t('timeline.quick_filter_settings') }"
+    :trigger-attrs="{ title: $t('timeline.quick_filter_settings') }"
   >
     <template #content>
       <div class="dropdown-menu">
diff --git a/src/components/quick_view_settings/quick_view_settings.vue b/src/components/quick_view_settings/quick_view_settings.vue
index d7c9bf3b..4bd81c5b 100644
--- a/src/components/quick_view_settings/quick_view_settings.vue
+++ b/src/components/quick_view_settings/quick_view_settings.vue
@@ -3,7 +3,7 @@
     trigger="click"
     class="QuickViewSettings"
     :bound-to="{ x: 'container' }"
-    :triggerAttrs="{ title: $t('timeline.quick_view_settings') }"
+    :trigger-attrs="{ title: $t('timeline.quick_view_settings') }"
   >
     <template #content>
       <div class="dropdown-menu">
diff --git a/src/components/search/search.js b/src/components/search/search.js
index 8d4212cd..877d6f30 100644
--- a/src/components/search/search.js
+++ b/src/components/search/search.js
@@ -83,11 +83,11 @@ const Search = {
         this.lastStatusFetchCount = 0
       }
 
-      this.$store.dispatch('search', { q: query, resolve: true, offset: this.statusesOffset, 'type': searchType })
+      this.$store.dispatch('search', { q: query, resolve: true, offset: this.statusesOffset, type: searchType })
         .then(data => {
           this.loading = false
 
-          let oldLength = this.statuses.length
+          const oldLength = this.statuses.length
 
           // Always append to old results. If new results are empty, this doesn't change anything
           this.userIds = this.userIds.concat(map(data.accounts, 'id'))
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 877a0cc0..2279f21a 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -6,8 +6,8 @@
         :timeline-name="timelineName"
       />
       <div
-        class="rightside-button"
         v-if="showScrollTop && !embedded"
+        class="rightside-button"
       >
         <button
           class="button-unstyled scroll-to-top-button"
@@ -26,8 +26,8 @@
       </div>
       <template v-if="mobileLayout && !embedded">
         <div
-          class="rightside-button"
           v-if="showLoadButton"
+          class="rightside-button"
         >
           <button
             class="button-unstyled loadmore-button"
@@ -72,8 +72,14 @@
           {{ $t('timeline.up_to_date') }}
         </div>
       </template>
-      <QuickFilterSettings v-if="!embedded" class="rightside-button"/>
-      <QuickViewSettings v-if="!embedded" class="rightside-button"/>
+      <QuickFilterSettings
+        v-if="!embedded"
+        class="rightside-button"
+      />
+      <QuickViewSettings
+        v-if="!embedded"
+        class="rightside-button"
+      />
     </div>
     <div :class="classes.body">
       <div
-- 
cgit v1.2.3-70-g09d2

