make more components use new popover, fix some things

This commit is contained in:
Shpuld Shpuldson 2020-02-14 09:05:33 +02:00
parent 94eeca3e7e
commit 3c136c241f
16 changed files with 166 additions and 186 deletions

View file

@ -12,7 +12,6 @@ import MobilePostStatusButton from './components/mobile_post_status_button/mobil
import MobileNav from './components/mobile_nav/mobile_nav.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import PopoverTarget from './components/popover/popover_target.vue'
import { windowWidth } from './services/window_utils/window_utils' import { windowWidth } from './services/window_utils/window_utils'
export default { export default {
@ -31,8 +30,7 @@ export default {
MobilePostStatusButton, MobilePostStatusButton,
MobileNav, MobileNav,
UserReportingModal, UserReportingModal,
PostStatusModal, PostStatusModal
PopoverTarget
}, },
data: () => ({ data: () => ({
mobileActivePanel: 'timeline', mobileActivePanel: 'timeline',

View file

@ -123,7 +123,6 @@
<UserReportingModal /> <UserReportingModal />
<PostStatusModal /> <PostStatusModal />
<portal-target name="modal" /> <portal-target name="modal" />
<PopoverTarget />
</div> </div>
</template> </template>

View file

@ -1,4 +1,5 @@
import ProgressButton from '../progress_button/progress_button.vue' import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
const AccountActions = { const AccountActions = {
props: [ props: [
@ -8,7 +9,8 @@ const AccountActions = {
return { } return { }
}, },
components: { components: {
ProgressButton ProgressButton,
Popover
}, },
methods: { methods: {
showRepeats () { showRepeats () {

View file

@ -1,13 +1,13 @@
<template> <template>
<div class="account-actions"> <div class="account-actions">
<v-popover <Popover
trigger="click" trigger="click"
class="account-tools-popover" placement="bottom"
:container="false" >
placement="bottom-end" <div
:offset="5" slot="content"
class="account-tools-popover"
> >
<div slot="popover">
<div class="dropdown-menu"> <div class="dropdown-menu">
<template v-if="user.following"> <template v-if="user.following">
<button <button
@ -51,10 +51,13 @@
</button> </button>
</div> </div>
</div> </div>
<div class="btn btn-default ellipsis-button"> <div
slot="trigger"
class="btn btn-default ellipsis-button"
>
<i class="icon-ellipsis trigger-button" /> <i class="icon-ellipsis trigger-button" />
</div> </div>
</v-popover> </Popover>
</div> </div>
</template> </template>
@ -67,6 +70,9 @@
margin: 0 .8em; margin: 0 .8em;
} }
.account-tools-popover {
}
.account-actions button.dropdown-item { .account-actions button.dropdown-item {
margin-left: 0; margin-left: 0;
} }

View file

@ -1,11 +1,13 @@
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import Popover from '../popover/popover.vue'
const EMOJI_REACTION_COUNT_CUTOFF = 12 const EMOJI_REACTION_COUNT_CUTOFF = 12
const EmojiReactions = { const EmojiReactions = {
name: 'EmojiReactions', name: 'EmojiReactions',
components: { components: {
UserAvatar UserAvatar,
Popover
}, },
props: ['status'], props: ['status'],
data: () => ({ data: () => ({

View file

@ -1,15 +1,14 @@
<template> <template>
<div class="emoji-reactions"> <div class="emoji-reactions">
<v-popover <Popover
v-for="(reaction) in emojiReactions" v-for="(reaction) in emojiReactions"
:key="reaction.name" :key="reaction.name"
:popper-options="popperOptions"
trigger="hover" trigger="hover"
placement="top" placement="top"
:offset="{ y: 5 }"
> >
<div <div
slot="popover" slot="content"
class="reacted-users" class="reacted-users"
> >
<div v-if="accountsForEmoji[reaction.name].length"> <div v-if="accountsForEmoji[reaction.name].length">
@ -34,6 +33,7 @@
</div> </div>
</div> </div>
<button <button
slot="trigger"
class="emoji-reaction btn btn-default" class="emoji-reaction btn btn-default"
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }" :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
@click="emojiOnClick(reaction.name, $event)" @click="emojiOnClick(reaction.name, $event)"
@ -42,7 +42,7 @@
<span class="reaction-emoji">{{ reaction.name }}</span> <span class="reaction-emoji">{{ reaction.name }}</span>
<span>{{ reaction.count }}</span> <span>{{ reaction.count }}</span>
</button> </button>
</v-popover> </Popover>
<a <a
v-if="tooManyReactions" v-if="tooManyReactions"
@click="toggleShowAll" @click="toggleShowAll"
@ -78,6 +78,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 0.5em; margin-left: 0.5em;
min-width: 5em;
img { img {
width: 1em; width: 1em;

View file

@ -47,7 +47,10 @@
</button> </button>
</div> </div>
</div> </div>
<div slot="trigger" class="button-icon"> <div
slot="trigger"
class="button-icon"
>
<i class="icon-ellipsis" /> <i class="icon-ellipsis" />
</div> </div>
</Popover> </Popover>
@ -59,10 +62,6 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
@import '../popper/popper.scss'; @import '../popper/popper.scss';
.dropdown-menu {
flex-shrink: 0;
}
.icon-ellipsis { .icon-ellipsis {
cursor: pointer; cursor: pointer;

View file

@ -4,7 +4,10 @@ const Popover = {
props: [ props: [
'trigger', 'trigger',
'placement', 'placement',
'show' 'boundTo',
'padding',
'offset',
'popoverClass'
], ],
data () { data () {
return { return {
@ -31,55 +34,77 @@ const Popover = {
} }
}, },
*/ */
showPopover () { updateStyles () {
this.hidden = false
this.$nextTick(function () {
if (this.hidden) return { opacity: 0 } if (this.hidden) return { opacity: 0 }
// Popover will be anchored around this element
const anchorEl = this.$refs.trigger || this.$el const anchorEl = this.$refs.trigger || this.$el
console.log(anchorEl)
const screenBox = anchorEl.getBoundingClientRect() 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 origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
const content = this.$refs.content const content = this.$refs.content
const parentBounds = this.boundTo === 'container' && this.$el.offsetParent.getBoundingClientRect()
const padding = this.padding || {}
const bounds = this.boundTo === 'container'
? {
xMin: parentBounds.left + (padding.left || 0),
xMax: parentBounds.right - (padding.right || 0),
yMin: 0 + (padding.top || 50),
yMax: window.innerHeight - (padding.bottom || 5)
} : {
xMin: 0 + (padding.left || 10),
xMax: window.innerWidth - (padding.right || 10),
yMin: 0 + (padding.top || 50),
yMax: window.innerHeight - (padding.bottom || 5)
}
let horizOffset = 0 let horizOffset = 0
if ((origin.x - content.offsetWidth * 0.5) < 25) { console.log(bounds, content.offsetWidth)
horizOffset += -(origin.x - content.offsetWidth * 0.5) + 25
// If overflowing from left, move it
if ((origin.x - content.offsetWidth * 0.5) < bounds.xMin) {
horizOffset = -(origin.x - content.offsetWidth * 0.5) + bounds.xMin
} }
console.log((origin.x + content.offsetWidth * 0.5), (window.innerWidth - 25)) // If overflowing from right, move it
if ((origin.x + content.offsetWidth * 0.5) > window.innerWidth - 25) { if ((origin.x + horizOffset + content.offsetWidth * 0.5) > bounds.xMax) {
horizOffset -= (origin.x + content.offsetWidth * 0.5) - (window.innerWidth - 25) horizOffset -= (origin.x + horizOffset + content.offsetWidth * 0.5) - bounds.xMax
} }
// Default to whatever user wished with placement prop // Default to whatever user wished with placement prop
let usingTop = this.placement !== 'bottom' let usingTop = this.placement !== 'bottom'
// Handle special cases, first force to displaying on top if there's not space on 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 // 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. // force to bottom, again regardless of what placement value was.
if (origin.y + content.offsetHeight > (window.innerHeight - 25)) usingTop = true if (origin.y + content.offsetHeight > bounds.yMax) usingTop = true
if (origin.y - content.offsetHeight < 50) usingTop = false if (origin.y - content.offsetHeight < bounds.yMin) usingTop = false
const vertAlign = usingTop ? const yOffset = (this.offset && this.offset.y) || 0
{ const vertAlign = usingTop
bottom: `${anchorEl.offsetHeight}px` ? {
} : bottom: `${anchorEl.offsetHeight + yOffset}px`
{ }
top: `${anchorEl.offsetHeight}px` : {
top: `${anchorEl.offsetHeight + yOffset}px`
} }
this.styles = { this.styles = {
opacity: '100%', opacity: '100%',
left: `${(anchorEl.offsetLeft + anchorEl.offsetWidth * 0.5) - content.offsetWidth * 0.5 + horizOffset}px`, left: `${(anchorEl.offsetLeft + anchorEl.offsetWidth * 0.5) - content.offsetWidth * 0.5 + horizOffset}px`,
...vertAlign ...vertAlign
} }
}) },
showPopover () {
if (this.hidden) this.$emit('show')
this.hidden = false
this.$nextTick(this.updateStyles)
}, },
hidePopover () { hidePopover () {
if (!this.hidden) this.$emit('close')
this.hidden = true this.hidden = true
this.styles = { opacity: 0 } this.styles = { opacity: 0 }
}, },
onMouseenter (e) { onMouseenter (e) {
console.log(this.trigger)
if (this.trigger === 'hover') this.showPopover() if (this.trigger === 'hover') this.showPopover()
}, },
onMouseleave (e) { onMouseleave (e) {
@ -97,15 +122,18 @@ const Popover = {
onClickOutside (e) { onClickOutside (e) {
if (this.hidden) return if (this.hidden) return
if (this.$el.contains(e.target)) return if (this.$el.contains(e.target)) return
console.log(e.target)
this.hidePopover() this.hidePopover()
} }
}, },
beforeUpdate () {
console.log('beforeupdate')
// if (!this.hidden) this.$nextTick(this.updateStyles)
},
created () { created () {
document.addEventListener("click", this.onClickOutside) document.addEventListener('click', this.onClickOutside)
}, },
destroyed () { destroyed () {
document.removeEventListener("click", this.onClickOutside) document.removeEventListener('click', this.onClickOutside)
this.hidePopover() this.hidePopover()
} }
} }

View file

@ -1,40 +1,26 @@
<template> <template>
<!-- This is for the weird portal shit
<div
@mouseenter="registerPopover"
@mouseleave="unregisterPopover"
>
<slot name="trigger"></slot>
<portal
v-if="targetId"
:to="targetId"
>
<slot name="content"></slot>
</portal>
</div>
-->
<div <div
@mouseenter="onMouseenter" @mouseenter="onMouseenter"
@mouseleave="onMouseleave" @mouseleave="onMouseleave"
> >
<div @click="onClick" ref="trigger"> <div
<slot name="trigger"></slot> ref="trigger"
@click="onClick"
>
<slot name="trigger" />
</div> </div>
<div <div
v-if="display" v-if="display"
ref="content"
:style="styles" :style="styles"
class="popover" class="popover"
:class="popoverClass"
> >
<div
ref="content"
class="popover-inner"
>
<!-- onSuccess is to mimic basic functionality of v-popover -->
<slot <slot
name="content" name="content"
@onSuccess="hidePopover" class="popover-inner"
></slot> :close="hidePopover"
</div> />
</div> </div>
</div> </div>
</template> </template>
@ -48,15 +34,14 @@
z-index: 8; z-index: 8;
position: absolute; position: absolute;
min-width: 0; min-width: 0;
transition: opacity 0.3s;
.popover-inner {
box-shadow: 1px 1px 4px rgba(0,0,0,.6); box-shadow: 1px 1px 4px rgba(0,0,0,.6);
box-shadow: var(--panelShadow); box-shadow: var(--panelShadow);
border-radius: $fallback--btnRadius; border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius); border-radius: var(--btnRadius, $fallback--btnRadius);
background-color: $fallback--bg; background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg); background-color: var(--bg, $fallback--bg);
}
.popover-arrow { .popover-arrow {
width: 0; width: 0;

View file

@ -1,34 +1,25 @@
import Popover from '../popover/popover.vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
const ReactButton = { const ReactButton = {
props: ['status', 'loggedIn'], props: ['status', 'loggedIn'],
data () { data () {
return { return {
showTooltip: false, filterWord: ''
filterWord: '',
popperOptions: {
modifiers: {
preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
}
}
} }
}, },
components: {
Popover
},
methods: { methods: {
openReactionSelect () { addReaction (event, emoji, close) {
this.showTooltip = true
this.filterWord = ''
},
closeReactionSelect () {
this.showTooltip = false
},
addReaction (event, emoji) {
const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji) const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
if (existingReaction && existingReaction.me) { if (existingReaction && existingReaction.me) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji }) this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
} else { } else {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
} }
this.closeReactionSelect() close()
} }
}, },
computed: { computed: {

View file

@ -1,13 +1,13 @@
<template> <template>
<v-popover <Popover
:popper-options="popperOptions" trigger="click"
:open="showTooltip"
trigger="manual"
placement="top" placement="top"
class="react-button-popover" class="react-button-popover"
@hide="closeReactionSelect"
> >
<div slot="popover"> <div
slot="content"
slot-scope="{close}"
>
<div class="reaction-picker-filter"> <div class="reaction-picker-filter">
<input <input
v-model="filterWord" v-model="filterWord"
@ -19,7 +19,7 @@
v-for="emoji in commonEmojis" v-for="emoji in commonEmojis"
:key="emoji" :key="emoji"
class="emoji-button" class="emoji-button"
@click="addReaction($event, emoji)" @click="addReaction($event, emoji, close)"
> >
{{ emoji }} {{ emoji }}
</span> </span>
@ -28,7 +28,7 @@
v-for="(emoji, key) in emojis" v-for="(emoji, key) in emojis"
:key="key" :key="key"
class="emoji-button" class="emoji-button"
@click="addReaction($event, emoji.replacement)" @click="addReaction($event, emoji.replacement, close)"
> >
{{ emoji.replacement }} {{ emoji.replacement }}
</span> </span>
@ -37,14 +37,14 @@
</div> </div>
<div <div
v-if="loggedIn" v-if="loggedIn"
@click.prevent="openReactionSelect" slot="trigger"
> >
<i <i
class="icon-smile button-icon add-reaction-button" class="icon-smile button-icon add-reaction-button"
:title="$t('tool_tip.add_reaction')" :title="$t('tool_tip.add_reaction')"
/> />
</div> </div>
</v-popover> </Popover>
</template> </template>
<script src="./react_button.js" ></script> <script src="./react_button.js" ></script>

View file

@ -177,6 +177,8 @@
<StatusPopover <StatusPopover
v-if="!isPreview" v-if="!isPreview"
:status-id="status.in_reply_to_status_id" :status-id="status.in_reply_to_status_id"
class="reply-to-popover"
style="min-width: 0"
> >
<a <a
class="reply-to" class="reply-to"
@ -564,11 +566,10 @@ $status-margin: 0.75em;
align-items: stretch; align-items: stretch;
> .reply-to-and-accountname > a { > .reply-to-and-accountname > a {
overflow: hidden;
max-width: 100%; max-width: 100%;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap; white-space: nowrap;
display: inline-block;
word-break: break-all; word-break: break-all;
} }
} }
@ -577,7 +578,6 @@ $status-margin: 0.75em;
display: flex; display: flex;
height: 18px; height: 18px;
margin-right: 0.5em; margin-right: 0.5em;
overflow: hidden;
max-width: 100%; max-width: 100%;
.icon-reply { .icon-reply {
transform: scaleX(-1); transform: scaleX(-1);
@ -588,6 +588,10 @@ $status-margin: 0.75em;
display: flex; display: flex;
} }
.reply-to-popover {
min-width: 0;
}
.reply-to { .reply-to {
display: flex; display: flex;
} }
@ -595,6 +599,7 @@ $status-margin: 0.75em;
.reply-to-text { .reply-to-text {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
margin: 0 0.4em 0 0.2em; margin: 0 0.4em 0 0.2em;
color: $fallback--faint; color: $fallback--faint;
color: var(--faint, $fallback--faint); color: var(--faint, $fallback--faint);

View file

@ -5,15 +5,6 @@ const StatusPopover = {
props: [ props: [
'statusId' 'statusId'
], ],
data () {
return {
popperOptions: {
modifiers: {
preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
}
}
}
},
computed: { computed: {
status () { status () {
return find(this.$store.state.statuses.allStatuses, { id: this.statusId }) return find(this.$store.state.statuses.allStatuses, { id: this.statusId })
@ -28,6 +19,7 @@ const StatusPopover = {
if (!this.status) { if (!this.status) {
this.$store.dispatch('fetchStatus', this.statusId) this.$store.dispatch('fetchStatus', this.statusId)
} }
console.log(this.$el.offsetParent)
} }
} }
} }

View file

@ -1,9 +1,16 @@
<template> <template>
<Popover trigger="hover"> <Popover
trigger="hover"
bound-to="container"
popover-class="status-popover"
@show="enter"
>
<template slot="trigger"> <template slot="trigger">
<slot /> <slot />
</template> </template>
<div slot="content" class="status-popover"> <div
slot="content"
>
<Status <Status
v-if="status" v-if="status"
:is-preview="true" :is-preview="true"
@ -31,7 +38,6 @@
max-width: 95%; max-width: 95%;
margin: 0.5em 0; margin: 0.5em 0;
.popover-inner {
border-color: $fallback--border; border-color: $fallback--border;
border-color: var(--border, $fallback--border); border-color: var(--border, $fallback--border);
border-style: solid; border-style: solid;
@ -40,7 +46,6 @@
border-radius: var(--tooltipRadius, $fallback--tooltipRadius); border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
box-shadow: var(--popupShadow); box-shadow: var(--popupShadow);
}
.popover-arrow::before { .popover-arrow::before {
position: absolute; position: absolute;

View file

@ -19,7 +19,6 @@ import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js' import reportsModule from './modules/reports.js'
import pollsModule from './modules/polls.js' import pollsModule from './modules/polls.js'
import postStatusModule from './modules/postStatus.js' import postStatusModule from './modules/postStatus.js'
import popoverModule from './modules/popover.js'
import VueI18n from 'vue-i18n' import VueI18n from 'vue-i18n'
@ -90,8 +89,7 @@ const persistedStateOptions = {
oauthTokens: oauthTokensModule, oauthTokens: oauthTokensModule,
reports: reportsModule, reports: reportsModule,
polls: pollsModule, polls: pollsModule,
postStatus: postStatusModule, postStatus: postStatusModule
popover: popoverModule
}, },
plugins: [persistedState, pushNotifications], plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now. strict: false // Socket modifies itself, let's ignore this for now.

View file

@ -1,31 +0,0 @@
import { omit } from 'lodash'
import { set } from 'vue'
const popover = {
state: {
popovers: {}
},
mutations: {
registerPopover (state, { id, el }) {
set(state.popovers, id, el)
},
unregisterPopover (state, { id }) {
state.popovers = omit(state.popovers, id)
}
},
actions: {
registerPopover (store, el) {
// Generate unique id, it will be used to link portal and portal-target
// popover-target will make portal targets for each registered popover
// el will be used by popover target to put popovers in their places.
let id = Math.floor(Math.random() * 1000000).toString()
store.commit('registerPopover', { id, el })
return id
},
unregisterPopover (store, id) {
store.commit('unregisterPopover', { id })
}
}
}
export default popover