From 846285326900a497c68dc994ce06dcb1d5d1ca9c Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 28 Jan 2020 22:40:15 +0200
Subject: update button toggled state, apply it to emoji reactions

---
 src/components/emoji_reactions/emoji_reactions.vue | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

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

diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 00d6d2b7..f5d2e79a 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -4,7 +4,7 @@
       v-for="(reaction) in emojiReactions"
       :key="reaction.emoji"
       class="emoji-reaction btn btn-default"
-      :class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
+      :class="{ 'toggled': reactedWith(reaction.emoji) }"
       @click="emojiOnClick(reaction.emoji, $event)"
     >
       <span class="reaction-emoji">{{ reaction.emoji }}</span>
@@ -40,10 +40,4 @@
   }
 }
 
-.picked-reaction {
-  border: 1px solid var(--link, $fallback--link);
-  margin-left: -1px; // offset the border, can't use inset shadows either
-  margin-right: calc(0.5em - 1px);
-}
-
 </style>
-- 
cgit v1.2.3-70-g09d2


From 2274976c09449838f3f6496c4d908a604d8d91d9 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 12 Feb 2020 01:10:00 +0200
Subject: post-merge fix

---
 src/components/emoji_reactions/emoji_reactions.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 290b25b3..77e08297 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -35,7 +35,7 @@
       </div>
       <button
         class="emoji-reaction btn btn-default"
-        :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
+        :class="{ 'toggled': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
         @click="emojiOnClick(reaction.name, $event)"
         @mouseenter="fetchEmojiReactionsByIfMissing()"
       >
-- 
cgit v1.2.3-70-g09d2


From e36647e95e78711398abdba224c945fd6570d1b6 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 17 Feb 2020 22:28:14 +0200
Subject: revert emoji reaction style

---
 src/components/emoji_reactions/emoji_reactions.vue | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

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

diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 77e08297..b25c9716 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -35,7 +35,7 @@
       </div>
       <button
         class="emoji-reaction btn btn-default"
-        :class="{ 'toggled': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
+        :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
         @click="emojiOnClick(reaction.name, $event)"
         @mouseenter="fetchEmojiReactionsByIfMissing()"
       >
@@ -127,4 +127,10 @@
   }
 }
 
+.picked-reaction {
+  border: 1px solid var(--accent, $fallback--link);
+  margin-left: -1px; // offset the border, can't use inset shadows either
+  margin-right: calc(0.5em - 1px);
+}
+
 </style>
-- 
cgit v1.2.3-70-g09d2


From 31225f5d142b51d52bed305f25a37288c9188062 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson <shp@cock.li>
Date: Fri, 28 Feb 2020 16:39:47 +0000
Subject: Fix/popover performance

---
 CHANGELOG.md                                       |   1 +
 package.json                                       |   1 -
 src/components/account_actions/account_actions.js  |   4 +-
 src/components/account_actions/account_actions.vue |  21 +--
 src/components/emoji_reactions/emoji_reactions.js  |  11 +-
 src/components/emoji_reactions/emoji_reactions.vue |  33 +++--
 src/components/extra_buttons/extra_buttons.js      |   3 +
 src/components/extra_buttons/extra_buttons.vue     |  14 +-
 .../moderation_tools/moderation_tools.js           |  11 +-
 .../moderation_tools/moderation_tools.vue          |  17 ++-
 src/components/popover/popover.js                  | 156 ++++++++++++++++++++
 src/components/popover/popover.vue                 | 118 +++++++++++++++
 src/components/popper/popper.scss                  | 164 ---------------------
 src/components/react_button/react_button.js        |  23 +--
 src/components/react_button/react_button.vue       |  32 ++--
 src/components/status/status.vue                   |  11 +-
 src/components/status_popover/status_popover.js    |  12 +-
 src/components/status_popover/status_popover.vue   |  64 +++-----
 src/main.js                                        |   8 -
 yarn.lock                                          |  19 ---
 20 files changed, 393 insertions(+), 330 deletions(-)
 create mode 100644 src/components/popover/popover.js
 create mode 100644 src/components/popover/popover.vue
 delete mode 100644 src/components/popper/popper.scss

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

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1cdd604b..e77334b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Registration fixed
 - Deactivation of remote accounts from frontend
 - Fixed NSFW unhiding not working with videos when using one-click unhiding/displaying
+- Improved performance of anything that uses popovers (most notably statuses)
 
 ## [1.1.7 and earlier] - 2019-12-14
 ### Added
diff --git a/package.json b/package.json
index 5c7fa31e..542086b4 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,6 @@
     "portal-vue": "^2.1.4",
     "sanitize-html": "^1.13.0",
     "v-click-outside": "^2.1.1",
-    "v-tooltip": "^2.0.2",
     "vue": "^2.5.13",
     "vue-chat-scroll": "^1.2.1",
     "vue-i18n": "^7.3.2",
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index d2153680..5d7ecf7e 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -1,4 +1,5 @@
 import ProgressButton from '../progress_button/progress_button.vue'
+import Popover from '../popover/popover.vue'
 
 const AccountActions = {
   props: [
@@ -8,7 +9,8 @@ const AccountActions = {
     return { }
   },
   components: {
-    ProgressButton
+    ProgressButton,
+    Popover
   },
   methods: {
     showRepeats () {
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index d3235be1..483783cf 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -1,13 +1,13 @@
 <template>
   <div class="account-actions">
-    <v-popover
+    <Popover
       trigger="click"
-      class="account-tools-popover"
-      :container="false"
-      placement="bottom-end"
-      :offset="5"
+      placement="bottom"
     >
-      <div slot="popover">
+      <div
+        slot="content"
+        class="account-tools-popover"
+      >
         <div class="dropdown-menu">
           <template v-if="user.following">
             <button
@@ -51,10 +51,13 @@
           </button>
         </div>
       </div>
-      <div class="btn btn-default ellipsis-button">
+      <div
+        slot="trigger"
+        class="btn btn-default ellipsis-button"
+      >
         <i class="icon-ellipsis trigger-button" />
       </div>
-    </v-popover>
+    </Popover>
   </div>
 </template>
 
@@ -62,7 +65,6 @@
 
 <style lang="scss">
 @import '../../_variables.scss';
-@import '../popper/popper.scss';
 .account-actions {
   margin: 0 .8em;
 }
@@ -70,6 +72,7 @@
 .account-actions button.dropdown-item {
   margin-left: 0;
 }
+
 .account-actions .trigger-button {
   color: $fallback--lightText;
   color: var(--lightText, $fallback--lightText);
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index b799ac9a..ae7f53be 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,20 +1,17 @@
 import UserAvatar from '../user_avatar/user_avatar.vue'
+import Popover from '../popover/popover.vue'
 
 const EMOJI_REACTION_COUNT_CUTOFF = 12
 
 const EmojiReactions = {
   name: 'EmojiReactions',
   components: {
-    UserAvatar
+    UserAvatar,
+    Popover
   },
   props: ['status'],
   data: () => ({
-    showAll: false,
-    popperOptions: {
-      modifiers: {
-        preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
-      }
-    }
+    showAll: false
   }),
   computed: {
     tooManyReactions () {
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index b25c9716..bac4c605 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,15 +1,14 @@
 <template>
   <div class="emoji-reactions">
-    <v-popover
+    <Popover
       v-for="(reaction) in emojiReactions"
       :key="reaction.name"
-      :popper-options="popperOptions"
       trigger="hover"
       placement="top"
+      :offset="{ y: 5 }"
     >
-
       <div
-        slot="popover"
+        slot="content"
         class="reacted-users"
       >
         <div v-if="accountsForEmoji[reaction.name].length">
@@ -24,7 +23,12 @@
               :compact="true"
             />
             <div class="reacted-user-names">
-              <span class="reacted-user-name" v-html="account.name_html" />
+              <!-- eslint-disable vue/no-v-html -->
+              <span
+                class="reacted-user-name"
+                v-html="account.name_html"
+              />
+              <!-- eslint-enable vue/no-v-html -->
               <span class="reacted-user-screen-name">{{ account.screen_name }}</span>
             </div>
           </div>
@@ -34,6 +38,7 @@
         </div>
       </div>
       <button
+        slot="trigger"
         class="emoji-reaction btn btn-default"
         :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
         @click="emojiOnClick(reaction.name, $event)"
@@ -42,17 +47,16 @@
         <span class="reaction-emoji">{{ reaction.name }}</span>
         <span>{{ reaction.count }}</span>
       </button>
-    </v-popover>
+    </Popover>
     <a
-        v-if="tooManyReactions"
-        @click="toggleShowAll"
-        class="emoji-reaction-expand faint"
-        href='javascript:void(0)'
-      >
-        {{ showAll ? $t('general.show_less') : showMoreString }}
-      </a>
+      v-if="tooManyReactions"
+      class="emoji-reaction-expand faint"
+      href="javascript:void(0)"
+      @click="toggleShowAll"
+    >
+      {{ showAll ? $t('general.show_less') : showMoreString }}
+    </a>
   </div>
-
 </template>
 
 <script src="./emoji_reactions.js" ></script>
@@ -78,6 +82,7 @@
     display: flex;
     flex-direction: column;
     margin-left: 0.5em;
+    min-width: 5em;
 
     img {
       width: 1em;
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index 5ac73e97..37485383 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -1,5 +1,8 @@
+import Popover from '../popover/popover.vue'
+
 const ExtraButtons = {
   props: [ 'status' ],
+  components: { Popover },
   methods: {
     deleteStatus () {
       const confirmed = window.confirm(this.$t('status.delete_confirm'))
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index 746f1c91..3a7f1283 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -1,11 +1,11 @@
 <template>
-  <v-popover
+  <Popover
     v-if="canDelete || canMute || canPin"
     trigger="click"
     placement="top"
     class="extra-button-popover"
   >
-    <div slot="popover">
+    <div slot="content">
       <div class="dropdown-menu">
         <button
           v-if="canMute && !status.thread_muted"
@@ -47,17 +47,17 @@
         </button>
       </div>
     </div>
-    <div class="button-icon">
-      <i class="icon-ellipsis" />
-    </div>
-  </v-popover>
+    <i
+      slot="trigger"
+      class="icon-ellipsis button-icon"
+    />
+  </Popover>
 </template>
 
 <script src="./extra_buttons.js" ></script>
 
 <style lang="scss">
 @import '../../_variables.scss';
-@import '../popper/popper.scss';
 
 .icon-ellipsis {
   cursor: pointer;
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index 757166ed..d4fdc53e 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -1,4 +1,5 @@
 import DialogModal from '../dialog_modal/dialog_modal.vue'
+import Popover from '../popover/popover.vue'
 
 const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
 const STRIP_MEDIA = 'mrf_tag:media-strip'
@@ -14,7 +15,6 @@ const ModerationTools = {
   ],
   data () {
     return {
-      showDropDown: false,
       tags: {
         FORCE_NSFW,
         STRIP_MEDIA,
@@ -24,11 +24,13 @@ const ModerationTools = {
         SANDBOX,
         QUARANTINE
       },
-      showDeleteUserDialog: false
+      showDeleteUserDialog: false,
+      toggled: false
     }
   },
   components: {
-    DialogModal
+    DialogModal,
+    Popover
   },
   computed: {
     tagsSet () {
@@ -89,6 +91,9 @@ const ModerationTools = {
             window.history.back()
           }
         })
+    },
+    setToggled (value) {
+      this.toggled = value
     }
   }
 }
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index e78e05f1..b2d5acc5 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -1,13 +1,14 @@
 <template>
   <div>
-    <v-popover
+    <Popover
       trigger="click"
       class="moderation-tools-popover"
-      placement="bottom-end"
-      @show="showDropDown = true"
-      @hide="showDropDown = false"
+      placement="bottom"
+      :offset="{ y: 5 }"
+      @show="setToggled(true)"
+      @close="setToggled(false)"
     >
-      <div slot="popover">
+      <div slot="content">
         <div class="dropdown-menu">
           <span v-if="user.is_local">
             <button
@@ -122,12 +123,13 @@
         </div>
       </div>
       <button
+        slot="trigger"
         class="btn btn-default btn-block"
-        :class="{ toggled: showDropDown }"
+        :class="{ toggled }"
       >
         {{ $t('user_card.admin_menu.moderation') }}
       </button>
-    </v-popover>
+    </Popover>
     <portal to="modal">
       <DialogModal
         v-if="showDeleteUserDialog"
@@ -160,7 +162,6 @@
 
 <style lang="scss">
 @import '../../_variables.scss';
-@import '../popper/popper.scss';
 
 .menu-checkbox {
   float: right;
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
new file mode 100644
index 00000000..5881d266
--- /dev/null
+++ b/src/components/popover/popover.js
@@ -0,0 +1,156 @@
+
+const Popover = {
+  name: 'Popover',
+  props: {
+    // Action to trigger popover: either 'hover' or 'click'
+    trigger: String,
+    // Either 'top' or 'bottom'
+    placement: String,
+    // Takes object with properties 'x' and 'y', values of these can be
+    // 'container' for using offsetParent as boundaries for either axis
+    // or 'viewport'
+    boundTo: Object,
+    // Takes a top/bottom/left/right object, how much space to leave
+    // between boundary and popover element
+    margin: Object,
+    // Takes a x/y object and tells how many pixels to offset from
+    // anchor point on either axis
+    offset: Object,
+    // Additional styles you may want for the popover container
+    popoverClass: String
+  },
+  data () {
+    return {
+      hidden: true,
+      styles: { opacity: 0 },
+      oldSize: { width: 0, height: 0 }
+    }
+  },
+  methods: {
+    updateStyles () {
+      if (this.hidden) {
+        this.styles = {
+          opacity: 0
+        }
+        return
+      }
+
+      // Popover will be anchored around this element, trigger ref is the container, so
+      // its children are what are inside the slot. Expect only one slot="trigger".
+      const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
+      const screenBox = anchorEl.getBoundingClientRect()
+      // Screen position of the origin point for popover
+      const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
+      const content = this.$refs.content
+      // Minor optimization, don't call a slow reflow call if we don't have to
+      const parentBounds = this.boundTo &&
+        (this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
+        this.$el.offsetParent.getBoundingClientRect()
+      const margin = this.margin || {}
+
+      // What are the screen bounds for the popover? Viewport vs container
+      // when using viewport, using default margin values to dodge the navbar
+      const xBounds = this.boundTo && this.boundTo.x === 'container' ? {
+        min: parentBounds.left + (margin.left || 0),
+        max: parentBounds.right - (margin.right || 0)
+      } : {
+        min: 0 + (margin.left || 10),
+        max: window.innerWidth - (margin.right || 10)
+      }
+
+      const yBounds = this.boundTo && this.boundTo.y === 'container' ? {
+        min: parentBounds.top + (margin.top || 0),
+        max: parentBounds.bottom - (margin.bottom || 0)
+      } : {
+        min: 0 + (margin.top || 50),
+        max: window.innerHeight - (margin.bottom || 5)
+      }
+
+      let horizOffset = 0
+
+      // If overflowing from left, move it so that it doesn't
+      if ((origin.x - content.offsetWidth * 0.5) < xBounds.min) {
+        horizOffset += -(origin.x - content.offsetWidth * 0.5) + xBounds.min
+      }
+
+      // If overflowing from right, move it so that it doesn't
+      if ((origin.x + horizOffset + content.offsetWidth * 0.5) > xBounds.max) {
+        horizOffset -= (origin.x + horizOffset + content.offsetWidth * 0.5) - xBounds.max
+      }
+
+      // Default to whatever user wished with placement prop
+      let usingTop = this.placement !== 'bottom'
+
+      // Handle special cases, first force to displaying on top if there's not space on bottom,
+      // regardless of what placement value was. Then check if there's not space on top, and
+      // force to bottom, again regardless of what placement value was.
+      if (origin.y + content.offsetHeight > yBounds.max) usingTop = true
+      if (origin.y - content.offsetHeight < yBounds.min) usingTop = false
+
+      const yOffset = (this.offset && this.offset.y) || 0
+      const translateY = usingTop
+        ? -anchorEl.offsetHeight - yOffset - content.offsetHeight
+        : yOffset
+
+      const xOffset = (this.offset && this.offset.x) || 0
+      const translateX = (anchorEl.offsetWidth * 0.5) - content.offsetWidth * 0.5 + horizOffset + xOffset
+
+      // Note, separate translateX and translateY avoids blurry text on chromium,
+      // single translate or translate3d resulted in blurry text.
+      this.styles = {
+        opacity: 1,
+        transform: `translateX(${Math.floor(translateX)}px) translateY(${Math.floor(translateY)}px)`
+      }
+    },
+    showPopover () {
+      if (this.hidden) this.$emit('show')
+      this.hidden = false
+      this.$nextTick(this.updateStyles)
+    },
+    hidePopover () {
+      if (!this.hidden) this.$emit('close')
+      this.hidden = true
+      this.styles = { opacity: 0 }
+    },
+    onMouseenter (e) {
+      if (this.trigger === 'hover') this.showPopover()
+    },
+    onMouseleave (e) {
+      if (this.trigger === 'hover') this.hidePopover()
+    },
+    onClick (e) {
+      if (this.trigger === 'click') {
+        if (this.hidden) {
+          this.showPopover()
+        } else {
+          this.hidePopover()
+        }
+      }
+    },
+    onClickOutside (e) {
+      if (this.hidden) return
+      if (this.$el.contains(e.target)) return
+      this.hidePopover()
+    }
+  },
+  updated () {
+    // Monitor changes to content size, update styles only when content sizes have changed,
+    // that should be the only time we need to move the popover box if we don't care about scroll
+    // or resize
+    const content = this.$refs.content
+    if (!content) return
+    if (this.oldSize.width !== content.offsetWidth || this.oldSize.height !== content.offsetHeight) {
+      this.updateStyles()
+      this.oldSize = { width: content.offsetWidth, height: content.offsetHeight }
+    }
+  },
+  created () {
+    document.addEventListener('click', this.onClickOutside)
+  },
+  destroyed () {
+    document.removeEventListener('click', this.onClickOutside)
+    this.hidePopover()
+  }
+}
+
+export default Popover
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
new file mode 100644
index 00000000..a271cb1b
--- /dev/null
+++ b/src/components/popover/popover.vue
@@ -0,0 +1,118 @@
+<template>
+  <div
+    @mouseenter="onMouseenter"
+    @mouseleave="onMouseleave"
+  >
+    <div
+      ref="trigger"
+      @click="onClick"
+    >
+      <slot name="trigger" />
+    </div>
+    <div
+      v-if="!hidden"
+      ref="content"
+      :style="styles"
+      class="popover"
+      :class="popoverClass"
+    >
+      <slot
+        name="content"
+        class="popover-inner"
+        :close="hidePopover"
+      />
+    </div>
+  </div>
+</template>
+
+<script src="./popover.js" />
+
+<style lang=scss>
+@import '../../_variables.scss';
+
+.popover {
+  z-index: 8;
+  position: absolute;
+  min-width: 0;
+  transition: opacity 0.3s;
+
+  box-shadow: 1px 1px 4px rgba(0,0,0,.6);
+  box-shadow: var(--panelShadow);
+  border-radius: $fallback--btnRadius;
+  border-radius: var(--btnRadius, $fallback--btnRadius);
+
+  background-color: $fallback--bg;
+  background-color: var(--popover, $fallback--bg);
+  color: $fallback--text;
+  color: var(--popoverText, $fallback--text);
+  --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);
+}
+
+.dropdown-menu {
+  display: block;
+  padding: .5rem 0;
+  font-size: 1rem;
+  text-align: left;
+  list-style: none;
+  max-width: 100vw;
+  z-index: 10;
+  white-space: nowrap;
+
+  .dropdown-divider {
+    height: 0;
+    margin: .5rem 0;
+    overflow: hidden;
+    border-top: 1px solid $fallback--border;
+    border-top: 1px solid var(--border, $fallback--border);
+  }
+
+  .dropdown-item {
+    line-height: 21px;
+    margin-right: 5px;
+    overflow: auto;
+    display: block;
+    padding: .25rem 1.0rem .25rem 1.5rem;
+    clear: both;
+    font-weight: 400;
+    text-align: inherit;
+    white-space: nowrap;
+    border: none;
+    border-radius: 0px;
+    background-color: transparent;
+    box-shadow: none;
+    width: 100%;
+    height: 100%;
+
+    --btnText: var(--popoverText, $fallback--text);
+
+    &-icon {
+      padding-left: 0.5rem;
+
+      i {
+        margin-right: 0.25rem;
+        color: var(--menuPopoverIcon, $fallback--icon)
+      }
+    }
+
+    &:active, &:hover {
+      background-color: $fallback--lightBg;
+      background-color: var(--selectedMenuPopover, $fallback--lightBg);
+      color: $fallback--link;
+      color: var(--selectedMenuPopoverText, $fallback--link);
+      --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
+      --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
+      --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
+      --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
+      i {
+        color: var(--selectedMenuPopoverIcon, $fallback--icon);
+      }
+    }
+
+  }
+}
+</style>
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
deleted file mode 100644
index 99b7e6fc..00000000
--- a/src/components/popper/popper.scss
+++ /dev/null
@@ -1,164 +0,0 @@
-@import '../../_variables.scss';
-
-.tooltip.popover {
-  z-index: 8;
-
-  .popover-inner {
-    box-shadow: 1px 1px 4px rgba(0,0,0,.6);
-    box-shadow: var(--panelShadow);
-    border-radius: $fallback--btnRadius;
-    border-radius: var(--btnRadius, $fallback--btnRadius);
-    background-color: $fallback--bg;
-    background-color: var(--popover, $fallback--bg);
-    color: $fallback--text;
-    color: var(--popoverText, $fallback--text);
-    --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);
-  }
-
-  .popover-arrow {
-    width: 0;
-    height: 0;
-    border-style: solid;
-    position: absolute;
-    margin: 5px;
-    border-color: $fallback--bg;
-    border-color: var(--bg, $fallback--bg);
-  }
-
-  &[x-placement^="top"] {
-    margin-bottom: 5px;
-
-    .popover-arrow {
-      border-width: 5px 5px 0 5px;
-      border-left-color: transparent !important;
-      border-right-color: transparent !important;
-      border-bottom-color: transparent !important;
-      bottom: -4px;
-      left: calc(50% - 5px);
-      margin-top: 0;
-      margin-bottom: 0;
-    }
-  }
-
-  &[x-placement^="bottom"] {
-    margin-top: 5px;
-
-    .popover-arrow {
-      border-width: 0 5px 5px 5px;
-      border-left-color: transparent !important;
-      border-right-color: transparent !important;
-      border-top-color: transparent !important;
-      top: -4px;
-      left: calc(50% - 5px);
-      margin-top: 0;
-      margin-bottom: 0;
-    }
-  }
-
-  &[x-placement^="right"] {
-    margin-left: 5px;
-
-    .popover-arrow {
-      border-width: 5px 5px 5px 0;
-      border-left-color: transparent !important;
-      border-top-color: transparent !important;
-      border-bottom-color: transparent !important;
-      left: -4px;
-      top: calc(50% - 5px);
-      margin-left: 0;
-      margin-right: 0;
-    }
-  }
-
-  &[x-placement^="left"] {
-    margin-right: 5px;
-
-    .popover-arrow {
-      border-width: 5px 0 5px 5px;
-      border-top-color: transparent !important;
-      border-right-color: transparent !important;
-      border-bottom-color: transparent !important;
-      right: -4px;
-      top: calc(50% - 5px);
-      margin-left: 0;
-      margin-right: 0;
-    }
-  }
-
-  &[aria-hidden='true'] {
-    visibility: hidden;
-    opacity: 0;
-    transition: opacity .15s, visibility .15s;
-  }
-
-  &[aria-hidden='false'] {
-    visibility: visible;
-    opacity: 1;
-    transition: opacity .15s;
-  }
-}
-
-.dropdown-menu {
-  display: block;
-  padding: .5rem 0;
-  font-size: 1rem;
-  text-align: left;
-  list-style: none;
-  max-width: 100vw;
-  z-index: 10;
-
-  .dropdown-divider {
-    height: 0;
-    margin: .5rem 0;
-    overflow: hidden;
-    border-top: 1px solid $fallback--border;
-    border-top: 1px solid var(--border, $fallback--border);
-  }
-
-  .dropdown-item {
-    line-height: 21px;
-    margin-right: 5px;
-    overflow: auto;
-    display: block;
-    padding: .25rem 1.0rem .25rem 1.5rem;
-    clear: both;
-    font-weight: 400;
-    text-align: inherit;
-    white-space: normal;
-    border: none;
-    border-radius: 0px;
-    background-color: transparent;
-    box-shadow: none;
-    width: 100%;
-    height: 100%;
-
-    --btnText: var(--popoverText, $fallback--text);
-
-    &-icon {
-      padding-left: 0.5rem;
-
-      i {
-        margin-right: 0.25rem;
-      }
-    }
-
-    &:active, &:hover {
-      background-color: $fallback--lightBg;
-      background-color: var(--selectedMenuPopover, $fallback--lightBg);
-      color: $fallback--link;
-      color: var(--selectedMenuPopoverText, $fallback--link);
-      --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
-      --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
-      --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
-      --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
-      i {
-        color: var(--selectedMenuPopoverIcon, $fallback--icon);
-      }
-    }
-  }
-}
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index a6cf5b94..19949563 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -1,34 +1,25 @@
+import Popover from '../popover/popover.vue'
 import { mapGetters } from 'vuex'
 
 const ReactButton = {
   props: ['status', 'loggedIn'],
   data () {
     return {
-      showTooltip: false,
-      filterWord: '',
-      popperOptions: {
-        modifiers: {
-          preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
-        }
-      }
+      filterWord: ''
     }
   },
+  components: {
+    Popover
+  },
   methods: {
-    openReactionSelect () {
-      this.showTooltip = true
-      this.filterWord = ''
-    },
-    closeReactionSelect () {
-      this.showTooltip = false
-    },
-    addReaction (event, emoji) {
+    addReaction (event, emoji, close) {
       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 })
       }
-      this.closeReactionSelect()
+      close()
     }
   },
   computed: {
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index fb43ebaf..ab4b4fcd 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -1,13 +1,14 @@
 <template>
-  <v-popover
-    :popper-options="popperOptions"
-    :open="showTooltip"
-    trigger="manual"
+  <Popover
+    trigger="click"
     placement="top"
+    :offset="{ y: 5 }"
     class="react-button-popover"
-    @hide="closeReactionSelect"
   >
-    <div slot="popover">
+    <div
+      slot="content"
+      slot-scope="{close}"
+    >
       <div class="reaction-picker-filter">
         <input
           v-model="filterWord"
@@ -19,7 +20,7 @@
           v-for="emoji in commonEmojis"
           :key="emoji"
           class="emoji-button"
-          @click="addReaction($event, emoji)"
+          @click="addReaction($event, emoji, close)"
         >
           {{ emoji }}
         </span>
@@ -28,23 +29,20 @@
           v-for="(emoji, key) in emojis"
           :key="key"
           class="emoji-button"
-          @click="addReaction($event, emoji.replacement)"
+          @click="addReaction($event, emoji.replacement, close)"
         >
           {{ emoji.replacement }}
         </span>
         <div class="reaction-bottom-fader" />
       </div>
     </div>
-    <div
+    <i
       v-if="loggedIn"
-      @click.prevent="openReactionSelect"
-    >
-      <i
-        class="icon-smile button-icon add-reaction-button"
-        :title="$t('tool_tip.add_reaction')"
-      />
-    </div>
-  </v-popover>
+      slot="trigger"
+      class="icon-smile button-icon add-reaction-button"
+      :title="$t('tool_tip.add_reaction')"
+    />
+  </Popover>
 </template>
 
 <script src="./react_button.js" ></script>
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 76b038d9..ca295640 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -177,6 +177,8 @@
                 <StatusPopover
                   v-if="!isPreview"
                   :status-id="status.in_reply_to_status_id"
+                  class="reply-to-popover"
+                  style="min-width: 0"
                 >
                   <a
                     class="reply-to"
@@ -572,11 +574,10 @@ $status-margin: 0.75em;
       align-items: stretch;
 
       > .reply-to-and-accountname > a {
+        overflow: hidden;
         max-width: 100%;
         text-overflow: ellipsis;
-        overflow: hidden;
         white-space: nowrap;
-        display: inline-block;
         word-break: break-all;
       }
     }
@@ -585,7 +586,6 @@ $status-margin: 0.75em;
       display: flex;
       height: 18px;
       margin-right: 0.5em;
-      overflow: hidden;
       max-width: 100%;
       .icon-reply {
         transform: scaleX(-1);
@@ -596,6 +596,10 @@ $status-margin: 0.75em;
       display: flex;
     }
 
+    .reply-to-popover {
+      min-width: 0;
+    }
+
     .reply-to {
       display: flex;
     }
@@ -603,6 +607,7 @@ $status-margin: 0.75em;
     .reply-to-text {
       overflow: hidden;
       text-overflow: ellipsis;
+      white-space: nowrap;
       margin: 0 0.4em 0 0.2em;
     }
 
diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js
index 19f16bd9..cb55f67e 100644
--- a/src/components/status_popover/status_popover.js
+++ b/src/components/status_popover/status_popover.js
@@ -5,22 +5,14 @@ const StatusPopover = {
   props: [
     'statusId'
   ],
-  data () {
-    return {
-      popperOptions: {
-        modifiers: {
-          preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
-        }
-      }
-    }
-  },
   computed: {
     status () {
       return find(this.$store.state.statuses.allStatuses, { id: this.statusId })
     }
   },
   components: {
-    Status: () => import('../status/status.vue')
+    Status: () => import('../status/status.vue'),
+    Popover: () => import('../popover/popover.vue')
   },
   methods: {
     enter () {
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
index eacf4c06..11f6cb5a 100644
--- a/src/components/status_popover/status_popover.vue
+++ b/src/components/status_popover/status_popover.vue
@@ -1,11 +1,16 @@
 <template>
-  <v-popover
+  <Popover
+    trigger="hover"
     popover-class="status-popover"
-    placement="top-start"
-    :popper-options="popperOptions"
-    @show="enter()"
+    :bound-to="{ x: 'container' }"
+    @show="enter"
   >
-    <template slot="popover">
+    <template slot="trigger">
+      <slot />
+    </template>
+    <div
+      slot="content"
+    >
       <Status
         v-if="status"
         :is-preview="true"
@@ -18,10 +23,8 @@
       >
         <i class="icon-spin4 animate-spin" />
       </div>
-    </template>
-
-    <slot />
-  </v-popover>
+    </div>
+  </Popover>
 </template>
 
 <script src="./status_popover.js" ></script>
@@ -29,44 +32,19 @@
 <style lang="scss">
 @import '../../_variables.scss';
 
-.tooltip.popover.status-popover {
+.status-popover {
   font-size: 1rem;
   min-width: 15em;
   max-width: 95%;
-  margin-left: 0.5em;
-
-  .popover-inner {
-    border-color: $fallback--border;
-    border-color: var(--border, $fallback--border);
-    border-style: solid;
-    border-width: 1px;
-    border-radius: $fallback--tooltipRadius;
-    border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
-    box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
-    box-shadow: var(--popupShadow);
-  }
 
-  .popover-arrow::before {
-    position: absolute;
-    content: '';
-    left: -7px;
-    border: solid 7px transparent;
-    z-index: -1;
-  }
-
-  &[x-placement^="bottom-start"] .popover-arrow::before {
-    top: -2px;
-    border-top-width: 0;
-    border-bottom-color: $fallback--border;
-    border-bottom-color: var(--border, $fallback--border);
-  }
-
-  &[x-placement^="top-start"] .popover-arrow::before {
-    bottom: -2px;
-    border-bottom-width: 0;
-    border-top-color: $fallback--border;
-    border-top-color: var(--border, $fallback--border);
-  }
+  border-color: $fallback--border;
+  border-color: var(--border, $fallback--border);
+  border-style: solid;
+  border-width: 1px;
+  border-radius: $fallback--tooltipRadius;
+  border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+  box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
+  box-shadow: var(--popupShadow);
 
   .status-el.status-el {
     border: none;
diff --git a/src/main.js b/src/main.js
index baf73ac8..be4213fa 100644
--- a/src/main.js
+++ b/src/main.js
@@ -31,7 +31,6 @@ import VueChatScroll from 'vue-chat-scroll'
 import VueClickOutside from 'v-click-outside'
 import PortalVue from 'portal-vue'
 import VBodyScrollLock from './directives/body_scroll_lock'
-import VTooltip from 'v-tooltip'
 
 import afterStoreSetup from './boot/after_store.js'
 
@@ -44,13 +43,6 @@ Vue.use(VueChatScroll)
 Vue.use(VueClickOutside)
 Vue.use(PortalVue)
 Vue.use(VBodyScrollLock)
-Vue.use(VTooltip, {
-  popover: {
-    defaultTrigger: 'hover click',
-    defaultContainer: false,
-    defaultOffset: 5
-  }
-})
 
 const i18n = new VueI18n({
   // By default, use the browser locale, we will update it if neccessary
diff --git a/yarn.lock b/yarn.lock
index b794042f..0defefcb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5941,11 +5941,6 @@ pngjs@^3.3.0:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
 
-popper.js@^1.15.0:
-  version "1.15.0"
-  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
-  integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
-
 portal-vue@^2.1.4:
   version "2.1.4"
   resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.4.tgz#1fc679d77e294dc8d026f1eb84aa467de11b392e"
@@ -7823,15 +7818,6 @@ v-click-outside@^2.1.1:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.3.tgz#b7297abe833a439dc0895e6418a494381e64b5e7"
 
-v-tooltip@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.2.tgz#8610d9eece2cc44fd66c12ef2f12eec6435cab9b"
-  integrity sha512-xQ+qzOFfywkLdjHknRPgMMupQNS8yJtf9Utd5Dxiu/0n4HtrxqsgDtN2MLZ0LKbburtSAQgyypuE/snM8bBZhw==
-  dependencies:
-    lodash "^4.17.11"
-    popper.js "^1.15.0"
-    vue-resize "^0.4.5"
-
 validate-npm-package-license@^3.0.1:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@@ -7906,11 +7892,6 @@ vue-loader@^14.0.0:
     vue-style-loader "^4.0.1"
     vue-template-es2015-compiler "^1.6.0"
 
-vue-resize@^0.4.5:
-  version "0.4.5"
-  resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea"
-  integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==
-
 vue-router@^3.0.1:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"
-- 
cgit v1.2.3-70-g09d2

