Merge branch 'disjointed-popovers' into shigusegubu

* disjointed-popovers: (29 commits)
  fix spacing in mentionsline
  add popovers to chats
  fix avatar not closing, add option to put popovers next to avatar instead of over it
  fix the incorrect rounding in nav list
  re-unfuck the timeline popover
  Revert "unify styling of timelines dropdown with other dropdown menus"
  close on avatar click again, add zooming as option
  fix basicusercard
  make hover popovers less annoying to close
  move tooltips setting
  unify styling of timelines dropdown with other dropdown menus
  put modals on top of navbar
  update changelog for vue3/threecolumn/popovers changes
  use nextrick with status popover updates
  fix invisible old popover, cleanup, add selection indicator in mentionlinks
  update status popover when its content changes (only status reference)
  slight z-index refactor and attempt at organizing it
  fix popovers not scrolling in mobile notifications
  fix spacings again
  fix tests by just ignoring the html comments
  ...
shigusegubu
Henry Jameson 2 years ago
commit c911356910
  1. 12
      CHANGELOG.md
  2. 9
      src/App.scss
  3. 2
      src/App.vue
  4. 3
      src/boot/after_store.js
  5. 12
      src/components/basic_user_card/basic_user_card.js
  6. 25
      src/components/basic_user_card/basic_user_card.vue
  7. 8
      src/components/chat_message/chat_message.js
  8. 6
      src/components/chat_message/chat_message.vue
  9. 10
      src/components/chat_title/chat_title.js
  10. 6
      src/components/chat_title/chat_title.vue
  11. 1
      src/components/desktop_nav/desktop_nav.scss
  12. 2
      src/components/desktop_nav/desktop_nav.vue
  13. 3
      src/components/emoji_picker/emoji_picker.scss
  14. 3
      src/components/extra_buttons/extra_buttons.js
  15. 2
      src/components/extra_buttons/extra_buttons.vue
  16. 2
      src/components/global_notice_list/global_notice_list.vue
  17. 2
      src/components/media_modal/media_modal.vue
  18. 20
      src/components/mention_link/mention_link.js
  19. 24
      src/components/mention_link/mention_link.scss
  20. 39
      src/components/mention_link/mention_link.vue
  21. 3
      src/components/mentions_line/mentions_line.vue
  22. 6
      src/components/mobile_nav/mobile_nav.vue
  23. 5
      src/components/modal/modal.vue
  24. 2
      src/components/nav_panel/nav_panel.vue
  25. 4
      src/components/notification/notification.js
  26. 22
      src/components/notification/notification.vue
  27. 10
      src/components/notifications/notifications.js
  28. 39
      src/components/popover/popover.js
  29. 6
      src/components/popover/popover.vue
  30. 16
      src/components/settings_modal/tabs/general_tab.vue
  31. 2
      src/components/shout_panel/shout_panel.vue
  32. 2
      src/components/side_drawer/side_drawer.vue
  33. 6
      src/components/status/status.js
  34. 30
      src/components/status/status.vue
  35. 7
      src/components/status_popover/status_popover.js
  36. 1
      src/components/status_popover/status_popover.vue
  37. 44
      src/components/timeline_menu/timeline_menu.vue
  38. 30
      src/components/user_card/user_card.js
  39. 51
      src/components/user_card/user_card.scss
  40. 38
      src/components/user_card/user_card.vue
  41. 23
      src/components/user_popover/user_popover.js
  42. 33
      src/components/user_popover/user_popover.vue
  43. 2
      src/components/user_profile/user_profile.vue
  44. 2
      src/i18n/en.json
  45. 2
      src/modules/config.js
  46. 15
      test/unit/specs/components/rich_content.spec.js

@ -16,17 +16,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
- Attachment description is prefilled with backend-provided default when uploading
- Proper visual feedback that next image is loading when browsing
- UI no longer lags when switching between mobile and desktop mode
- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything
- "Always show mobile button" is working now
### Changed
- Using Vue 3 now
- (You)s are optional (opt-in) now, bolding your nickname is also optional (opt-out)
- User highlight background now also covers the `@`
- Reverted back to textual `@`, svg version is opt-in.
- Settings window has been throughly rearranged to make make more sense and make navication settings easier.
- Settings window has been thoroughly rearranged to make more sense and make navigation settings easier.
- Uploaded attachments are uniform with displayed attachments
- Flash is watchable in media-modal (takes up nearly full screen though due to sizing issues)
- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post.
- Slight width/spacing adjustments
- More sizing stuff is font-size dependent now
- Scrollbars are styled/colorized now
- Scrollbars are toggleable (for stuff that didn't have visible scrollbars before) (opt-in)
### Added
- 3 column mode: only enables when there's space for it (opt-out, customizable)
- Options to show domains in mentions
- Option to show user avatars in mention links (opt-in)
- Option to disable the tooltip for mentions
@ -37,6 +46,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Media modal now also displays description and counter position in gallery (i.e. 1/5)
- Ability to rearrange order of attachments when uploading
- Enabled users to zoom and pan images in media viewer with mouse and touch
- Timelines/panels and conversations have sticky headers now
- Added frontend ui for account migration

@ -4,6 +4,13 @@
:root {
--navbar-height: 3.5rem;
--post-line-height: 1.4;
// Z-Index stuff
--ZI_media_modal: 90000;
--ZI_modals_popovers: 85000;
--ZI_modals: 80000;
--ZI_navbar_popovers: 75000;
--ZI_navbar: 70000;
--ZI_popovers: 60000;
}
html {
@ -117,7 +124,7 @@ i[class*=icon-],
}
nav {
z-index: 1000;
z-index: var(--ZI_navbar);
color: var(--topBarText);
background-color: $fallback--fg;
background-color: var(--topBar, $fallback--fg);

@ -42,7 +42,7 @@
</div>
<div id="notifs-column" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"/>
</div>
<media-modal />
<MediaModal />
<shout-panel
v-if="currentUser && shout && !hideShoutbox"
:floating="true"

@ -396,6 +396,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers)
// remove after vue 3.3
app.config.unwrapInjectedRef = true
app.mount('#app')
return app

@ -1,4 +1,4 @@
import UserCard from '../user_card/user_card.vue'
import UserPopover from '../user_popover/user_popover.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -7,20 +7,12 @@ const BasicUserCard = {
props: [
'user'
],
data () {
return {
userExpanded: false
}
},
components: {
UserCard,
UserPopover,
UserAvatar,
RichContent
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}

@ -1,24 +1,19 @@
<template>
<div class="basic-user-card">
<router-link :to="userProfileLink(user)">
<router-link @click.prevent :to="userProfileLink(user)">
<UserPopover
:userId="user.id"
:overlayCenters="true"
overlayCentersSelector=".avatar"
>
<UserAvatar
class="avatar"
class="user-avatar avatar"
:user="user"
@click.prevent="toggleUserExpanded"
@click.prevent
/>
</UserPopover>
</router-link>
<div
v-if="userExpanded"
class="basic-user-card-expanded-content"
>
<UserCard
:user-id="user.id"
:rounded="true"
:bordered="true"
/>
</div>
<div
v-else
class="basic-user-card-collapsed-content"
>
<div
@ -53,6 +48,8 @@
margin: 0;
padding: 0.6em 1em;
--emoji-size: 14px;
&-collapsed-content {
margin-left: 0.7em;
text-align: left;

@ -6,7 +6,7 @@ import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import StatusContent from '../status_content/status_content.vue'
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
@ -35,7 +35,8 @@ const ChatMessage = {
UserAvatar,
Gallery,
LinkPreview,
ChatMessageDate
ChatMessageDate,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
computed: {
// Returns HH:MM (hours and minutes) in local time.
@ -49,9 +50,6 @@ const ChatMessage = {
message () {
return this.chatViewItem.data
},
userProfileLink () {
return generateProfileLink(this.author.id, this.author.screen_name, this.$store.state.instance.restrictedNicknames)
},
isMessage () {
return this.chatViewItem.type === 'message'
},

@ -14,16 +14,16 @@
v-if="!isCurrentUser"
class="avatar-wrapper"
>
<router-link
<UserPopover
v-if="chatViewItem.isHead"
:to="userProfileLink"
:userId="author.id"
>
<UserAvatar
:compact="true"
:better-shadow="betterShadow"
:user="author"
/>
</router-link>
</UserPopover>
</div>
<div class="chat-message-inner">
<div

@ -1,12 +1,13 @@
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import UserAvatar from '../user_avatar/user_avatar.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import { defineAsyncComponent } from 'vue'
export default {
name: 'ChatTitle',
components: {
UserAvatar,
RichContent
RichContent,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
props: [
'user', 'withAvatar'
@ -18,10 +19,5 @@ export default {
htmlTitle () {
return this.user ? this.user.name_html : ''
}
},
methods: {
getUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name)
}
}
}

@ -3,16 +3,16 @@
class="chat-title"
:title="title"
>
<router-link
<UserPopover
class="avatar-container"
v-if="withAvatar && user"
:to="getUserProfileLink(user)"
:userId="user.id"
>
<UserAvatar
class="titlebar-avatar"
:user="user"
/>
</router-link>
</UserPopover>
<RichContent
v-if="user"
class="username"

@ -2,6 +2,7 @@
.DesktopNav {
width: 100%;
z-index: var(--ZI_navbar);
input {
color: var(--inputTopbarText, var(--inputText));

@ -38,7 +38,7 @@
/>
<button
class="button-unstyled nav-icon"
@click.stop="openSettingsModal"
@click="openSettingsModal"
>
<FAIcon
fixed-width

@ -7,7 +7,8 @@
right: 0;
left: 0;
margin: 0 !important;
z-index: 100;
// 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;

@ -89,6 +89,9 @@ const ExtraButtons = {
canMute () {
return !!this.currentUser
},
canBookmark () {
return !!this.currentUser
},
statusLink () {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
}

@ -51,6 +51,7 @@
icon="thumbtack"
/><span>{{ $t("status.unpin") }}</span>
</button>
<template v-if="canBookmark">
<button
v-if="!status.bookmarked"
class="button-default dropdown-item dropdown-item-icon"
@ -73,6 +74,7 @@
icon="bookmark"
/><span>{{ $t("status.unbookmark") }}</span>
</button>
</template>
<button
v-if="canDelete"
class="button-default dropdown-item dropdown-item-icon"

@ -32,7 +32,7 @@
top: 50px;
width: 100%;
pointer-events: none;
z-index: 1001;
z-index: var(--ZI_popovers);
display: flex;
flex-direction: column;
align-items: center;

@ -121,7 +121,7 @@ $modal-view-button-icon-width: 3em;
$modal-view-button-icon-margin: 0.5em;
.modal-view.media-modal-view {
z-index: 9000;
z-index: var(--ZI_media_modal);
flex-direction: column;
.modal-view-button-arrow,

@ -16,8 +16,7 @@ const MentionLink = {
name: 'MentionLink',
components: {
UserAvatar,
Popover: defineAsyncComponent(() => import('../popover/popover.vue')),
UserCard: defineAsyncComponent(() => import('../user_card/user_card.vue'))
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
props: {
url: {
@ -37,6 +36,11 @@ const MentionLink = {
type: String
}
},
data () {
return {
hasSelection: false
}
},
methods: {
onClick () {
if (this.shouldShowTooltip) return
@ -45,8 +49,17 @@ const MentionLink = {
this.userScreenName || this.user.screen_name
)
this.$router.push(link)
},
handleSelection () {
this.hasSelection = document.getSelection().containsNode(this.$refs.full, true)
}
},
mounted () {
document.addEventListener('selectionchange', this.handleSelection)
},
unmounted () {
document.removeEventListener('selectionchange', this.handleSelection)
},
computed: {
user () {
return this.url && this.$store && this.$store.getters.findUserByUrl(this.url)
@ -92,7 +105,8 @@ const MentionLink = {
return [
{
'-you': this.isYou && this.shouldBoldenYou,
'-highlighted': this.highlight
'-highlighted': this.highlight,
'-has-selection': this.hasSelection
},
this.highlightType
]

@ -55,11 +55,14 @@
.new {
&.-you {
& .shortName,
& .full {
.shortName {
font-weight: 600;
}
}
&.-has-selection {
color: var(--alertNeutralText, $fallback--text);
background-color: var(--alertNeutral, $fallback--fg);
}
.at {
color: var(--link);
@ -72,8 +75,7 @@
}
&.-striped {
& .shortName,
& .full {
& .shortName {
background-image:
repeating-linear-gradient(
135deg,
@ -86,31 +88,25 @@
}
&.-solid {
& .shortName,
& .full {
.shortName {
background-image: linear-gradient(var(--____highlight-tintColor2), var(--____highlight-tintColor2));
}
}
&.-side {
& .shortName,
& .userNameFull {
.shortName {
box-shadow: 0 -5px 3px -4px inset var(--____highlight-solidColor);
}
}
}
&:hover .new .full {
pointer-events: initial;
.full {
pointer-events: none;
}
.serverName.-faded {
color: var(--faintLink, $fallback--link);
}
.full .-faded {
color: var(--faint, $fallback--faint);
}
}
.mention-link-popover {

@ -10,14 +10,11 @@
target="_blank"
v-html="content"
/><!-- eslint-enable vue/no-v-html -->
<Popover
trigger="click"
:bound-to="{ x: 'container'}"
bound-to-selector=".column"
popover-class="popover-default mention-popover"
<UserPopover
v-else
:userId="user.id"
:disabled="!shouldShowTooltip"
>
<template v-slot:trigger>
<span
v-if="user"
class="new"
@ -57,37 +54,13 @@
:class="{ '-you': shouldBoldenYou }"
> {{ ' ' + $t('status.you') }}</span>
<!-- eslint-enable vue/no-v-html -->
</a><span
v-if="shouldShowTooltip"
class="full"
>
<span
class="userNameFull"
>
</a><span class="full" ref="full">
<!-- eslint-disable vue/no-v-html -->
@<span
class="userName"
v-html="userName"
/><span
class="serverName"
:class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName"
/>
@<span v-html="userName" /><span v-html="'@' + serverName" />
<!-- eslint-enable vue/no-v-html -->
</span>
</span>
</span></template>
<template v-slot:content>
<UserCard
class="mention-link-popover"
:user-id="user.id"
:hide-bio="true"
:bordered="false"
:allow-zooming-avatar="true"
:rounded="true"
/>
</template>
</Popover>
</UserPopover>
</span>
</template>

@ -13,8 +13,7 @@
<span
v-if="expanded"
class="fullExtraMentions"
>
<MentionLink
>{{ ' ' }}<MentionLink
v-for="mention in extraMentions"
:key="mention.index"
class="mention-link"

@ -86,6 +86,8 @@
@import '../../_variables.scss';
.MobileNav {
z-index: var(--ZI_navbar);
.mobile-nav {
display: grid;
line-height: var(--navbar-height);
@ -147,7 +149,7 @@
transition-property: transform;
transition-duration: 0.25s;
transform: translateX(0);
z-index: 1001;
z-index: var(--ZI_navbar);
-webkit-overflow-scrolling: touch;
&.-closed {
@ -160,7 +162,7 @@
display: flex;
align-items: center;
justify-content: space-between;
z-index: 1;
z-index: calc(var(--ZI_navbar) + 100);
width: 100%;
height: 50px;
line-height: 50px;

@ -22,6 +22,9 @@ export default {
default: false
}
},
provide: {
popoversZLayer: 'modals'
},
computed: {
classes () {
return {
@ -35,7 +38,7 @@ export default {
<style lang="scss">
.modal-view {
z-index: 2000;
z-index: var(--ZI_modals);
position: fixed;
top: 0;
left: 0;

@ -113,7 +113,9 @@
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
padding: 0;
}
> li {
&:first-child .menu-item {
border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);

@ -5,7 +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 RichContent from 'src/components/rich_content/rich_content.jsx'
import Popover from '../popover/popover.vue'
import UserPopover from '../user_popover/user_popover.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -48,7 +48,7 @@ const Notification = {
Timeago,
Status,
RichContent,
Popover
UserPopover
},
methods: {
toggleUserExpanded () {

@ -36,13 +36,10 @@
:href="$router.resolve(userProfileLink).href"
@click.prevent
>
<Popover
trigger="click"
popover-class="popover-default user-popover"
:overlay-centers="true"
overlay-centers-selector=".user-info-avatar-link .Avatar"
<UserPopover
:userId="notification.from_profile.id"
:overlayCenters="true"
>
<template v-slot:trigger>
<UserAvatar
class="post-avatar"
:bot="botIndicator"
@ -50,18 +47,7 @@
:better-shadow="betterShadow"
:user="notification.from_profile"
/>
</template>
<template v-slot:content>
<UserCard
class="mention-link-popover"
:user-id="getUser(notification).id"
:hide-bio="true"
:bordered="false"
:allow-zooming-avatar="true"
:rounded="true"
/>
</template>
</Popover>
</UserPopover>
</a>
<div class="notification-right">
<span class="notification-details">

@ -1,3 +1,4 @@
import { computed } from 'vue'
import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue'
import NotificationFilters from './notification_filters.vue'
@ -40,6 +41,11 @@ const Notifications = {
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
}
},
provide () {
return {
popoversZLayer: computed(() => this.popoversZLayer)
}
},
computed: {
mainClass () {
return this.minimalMode ? '' : 'panel panel-default'
@ -77,6 +83,10 @@ const Notifications = {
}
return map[layoutType] || '#notifs-sidebar'
},
popoversZLayer () {
const { layoutType } = this.$store.state.interface
return layoutType === 'mobile' ? 'navbar' : null
},
notificationsToDisplay () {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},

@ -42,11 +42,17 @@ const Popover = {
// What selector (witin popover!) to use for determining center of popover
overlayCentersSelector: String
},
inject: ['popoversZLayer'], // override popover z layer
data () {
return {
// 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
lockReEntry: false,
hidden: true,
styles: {},
oldSize: { width: 0, height: 0 },
scrollable: null,
// used to avoid blinking if hovered onto popover
graceTimeout: null
}
@ -80,7 +86,7 @@ const Popover = {
y: anchorScreenBox.top + anchorHeight * 0.5
}
const content = this.$refs.content
const overlayCenter = this.overlayCentersSelector
const overlayCenter = this.overlayCenters
? this.$refs.content.querySelector(this.overlayCentersSelector)
: null
@ -167,6 +173,9 @@ const Popover = {
top: `${Math.round(translateY)}px`
}
if (this.popoversZLayer) {
this.styles['--ZI_popover_override'] = `var(--ZI_${this.popoversZLayer}_popovers)`
}
if (parentScreenBox) {
this.styles.maxWidth = `${Math.round(parentScreenBox.width)}px`
}
@ -175,17 +184,29 @@ const Popover = {
if (this.disabled) return
const wasHidden = this.hidden
this.hidden = false
if (this.trigger === 'click') {
document.addEventListener('click', this.onClickOutside)
}
this.scrollable.addEventListener('scroll', this.onScroll)
this.scrollable.addEventListener('resize', this.onResize)
this.$nextTick(() => {
if (wasHidden) this.$emit('show')
this.updateStyles()
})
},
hidePopover () {
if (this.disabled) return
if (!this.hidden) this.$emit('close')
this.hidden = true
if (this.trigger === 'click') {
document.removeEventListener('click', this.onClickOutside)
}
this.scrollable.removeEventListener('scroll', this.onScroll)
this.scrollable.removeEventListener('resize', this.onResize)
},
onMouseenter (e) {
if (this.trigger === 'hover') {
this.lockReEntry = false
clearTimeout(this.graceTimeout)
this.graceTimeout = null
this.showPopover()
@ -197,7 +218,8 @@ const Popover = {
}
},
onMouseenterContent (e) {
if (this.trigger === 'hover') {
if (this.trigger === 'hover' && !this.lockReEntry) {
this.lockReEntry = true
clearTimeout(this.graceTimeout)
this.graceTimeout = null
this.showPopover()
@ -225,6 +247,9 @@ const Popover = {
},
onScroll (e) {
this.updateStyles()
},
onResize (e) {
this.updateStyles()
}
},
updated () {
@ -239,16 +264,12 @@ const Popover = {
}
},
mounted () {
let scrollable = this.$refs.trigger.closest('.column.-scrollable')
let scrollable = this.$refs.trigger.closest('.column.-scrollable') ||
this.$refs.trigger.closest('.mobile-notifications')
if (!scrollable) scrollable = window
document.addEventListener('click', this.onClickOutside)
scrollable.addEventListener('scroll', this.onScroll)
this.scrollable = scrollable
},
beforeUnmount () {
let scrollable = this.$refs.trigger.closest('.column.-scrollable')
if (!scrollable) scrollable = window
document.removeEventListener('click', this.onClickOutside)
scrollable.removeEventListener('scroll', this.onScroll)
this.hidePopover()
}
}

@ -43,10 +43,10 @@
}
.popover {
z-index: 90000;
z-index: var(--ZI_popover_override, var(--ZI_popovers));
position: fixed;
min-width: 0;
max-width: 90vw;
max-width: calc(100vw - 20px);
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
box-shadow: var(--popupShadow);
}
@ -87,7 +87,7 @@
text-align: left;
list-style: none;
max-width: 100vw;
z-index: 200;
z-index: var(--ZI_popover_override, var(--ZI_popovers));
white-space: nowrap;
.dropdown-divider {

@ -74,6 +74,16 @@
{{ $t('settings.show_scrollbars') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="userPopoverZoom">
{{ $t('settings.user_popover_avatar_zoom') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="userPopoverOverlay">
{{ $t('settings.user_popover_avatar_overlay') }}
</BooleanSetting>
</li>
<li>
<ChoiceSetting
v-if="user"
@ -261,10 +271,7 @@
{{ $t('settings.mention_link_display') }}
</ChoiceSetting>
</li>
<ul
class="setting-list suboptions"
>
<li v-if="mentionLinkDisplay === 'short'">
<li>
<BooleanSetting
path="mentionLinkShowTooltip"
expert="1"
@ -272,7 +279,6 @@
{{ $t('settings.mention_link_use_tooltip') }}
</BooleanSetting>
</li>
</ul>
<li>
<BooleanSetting
path="useAtIcon"

@ -80,7 +80,7 @@
.floating-shout {
position: fixed;
bottom: 0.5em;
z-index: 1000;
z-index: var(--ZI_popovers);
max-width: 25em;
&.-left {

@ -211,7 +211,7 @@
.side-drawer-container {
position: fixed;
z-index: 1000;
z-index: var(--ZI_navbar);
top: 0;
left: 0;
width: 100%;

@ -4,14 +4,13 @@ import ReactButton from '../react_button/react_button.vue'
import RetweetButton from '../retweet_button/retweet_button.vue'
import ExtraButtons from '../extra_buttons/extra_buttons.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import UserCard from '../user_card/user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue'
import StatusContent from '../status_content/status_content.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import StatusPopover from '../status_popover/status_popover.vue'
import Popover from '../popover/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 MentionsLine from 'src/components/mentions_line/mentions_line.vue'
@ -106,7 +105,6 @@ const Status = {
RetweetButton,
ExtraButtons,
PostStatusForm,
UserCard,
UserAvatar,
AvatarList,
Timeago,
@ -117,7 +115,7 @@ const Status = {
RichContent,
MentionLink,
MentionsLine,
Popover
UserPopover
},
props: [
'statusoid',

@ -123,13 +123,10 @@
class="left-side"
>
<a :href="$router.resolve(userProfileLink).href" @click.prevent>
<Popover
trigger="click"
popover-class="popover-default user-popover"
:overlay-centers="true"
overlay-centers-selector=".user-info-avatar-link .Avatar"
<UserPopover
:userId="status.user.id"
:overlayCenters="true"
>
<template v-slot:trigger>
<UserAvatar
class="post-avatar"
:bot="botIndicator"
@ -137,28 +134,10 @@
:better-shadow="betterShadow"
:user="status.user"
/>
</template>
<template v-slot:content>
<UserCard
class="mention-link-popover"
:user-id="status.user.id"
:hide-bio="true"
:bordered="false"
:allow-zooming-avatar="true"
:rounded="true"
/>
</template>
</Popover>
</UserPopover>
</a>
</div>
<div class="right-side">
<UserCard
v-if="userExpanded"
:user-id="status.user.id"
:rounded="true"
:bordered="true"
class="usercard"
/>
<div
v-if="!noHeading"
class="status-heading"
@ -338,6 +317,7 @@
class="mentions-line-first"
/>
</span>
{{ ' ' }}
<MentionsLine
v-if="hasMentionsLine"
:mentions="mentionsLine.slice(1)"

@ -38,6 +38,13 @@ const StatusPopover = {
.catch(e => (this.error = true))
}
}
},
watch: {
status (newStatus, oldStatus) {
if (newStatus !== oldStatus) {
this.$nextTick(() => this.$refs.popover.updateStyles())
}
}
}
}

@ -4,6 +4,7 @@
popover-class="popover-default status-popover"
:bound-to="{ x: 'container' }"
@show="enter"
ref="popover"
>
<template v-slot:trigger>
<slot />

@ -3,19 +3,17 @@
trigger="click"
class="TimelineMenu"
:class="{ 'open': isOpen }"
:margin="{ left: -15, right: -200 }"
:bound-to="{ x: 'container' }"
popover-class="timeline-menu-popover-wrap"
bound-to-selector=".Timeline"
popover-class="timeline-menu-popover popover-default"
@show="openMenu"
@close="() => isOpen = false"
>
<template v-slot:content>
<div class="timeline-menu-popover popover-default">
<TimelineMenuContent />
</div>
</template>
<template v-slot:trigger>
<button class="button-unstyled title timeline-menu-title">
<span class="button-unstyled title timeline-menu-title">
<span class="timeline-title">{{ timelineName() }}</span>
<span>
<FAIcon
@ -27,7 +25,7 @@
class="click-blocker"
@click="blockOpen"
/>
</button>
</span>
</template>
</Popover>
</template>
@ -38,42 +36,18 @@
@import '../../_variables.scss';
.TimelineMenu {
flex-shrink: 1;
margin-right: auto;
min-width: 0;
width: 24rem;
.popover-trigger-button {
vertical-align: bottom;
}
.timeline-menu-popover-wrap {
overflow: hidden;
// Match panel heading padding to line up menu with bottom of heading
margin-top: 0.6rem;
padding: 0 15px 15px 15px;
}
.timeline-menu-popover {
width: 24rem;
max-width: 100vw;
margin: 0;
font-size: 1rem;
border-top-right-radius: 0;
border-top-left-radius: 0;
transform: translateY(-100%);
transition: transform 100ms;
}
.panel::after {
border-top-right-radius: 0;
border-top-left-radius: 0;
}
&.open .timeline-menu-popover {
transform: translateY(0);
}
.timeline-menu-title {
margin: 0;
cursor: pointer;
@ -108,6 +82,16 @@
box-shadow: var(--popoverShadow);
}
}
.timeline-menu-popover {
min-width: 24rem;
max-width: 100vw;
margin-top: 0.6rem;
font-size: 1rem;
border-top-right-radius: 0;
border-top-left-radius: 0;
ul {
list-style: none;
margin: 0;

@ -14,7 +14,9 @@ import {
faRss,
faSearchPlus,
faExternalLinkAlt,
faEdit
faEdit,
faTimes,
faExpandAlt
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -22,12 +24,21 @@ library.add(
faBell,
faSearchPlus,
faExternalLinkAlt,
faEdit
faEdit,
faTimes,
faExpandAlt
)
export default {
props: [
'userId', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar'
'userId',
'switcher',
'selected',
'hideBio',
'rounded',
'bordered',
'avatarAction', // default - open profile, 'zoom' - zoom, function - call function
'onClose'
],
data () {
return {
@ -47,9 +58,10 @@ export default {
},
classes () {
return [{
'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
'user-card-rounded': this.rounded === true, // set border-radius for all sides
'user-card-bordered': this.bordered === true // set border for all sides
'-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius
'-rounded': this.rounded === true, // set border-radius for all sides
'-bordered': this.bordered === true, // set border for all sides
'-popover': !!this.onClose // set popover rounding
}]
},
style () {
@ -170,6 +182,12 @@ export default {
},
mentionUser () {
this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
},
onAvatarClickHandler (e) {
if (this.onAvatarClick) {
e.preventDefault()
this.onAvatarClick()
}
}
}
}

@ -42,8 +42,10 @@
mask-composite: exclude;
background-size: cover;
mask-size: 100% 60%;
border-top-left-radius: calc(var(--panelRadius) - 1px);
border-top-right-radius: calc(var(--panelRadius) - 1px);
border-top-left-radius: calc(var(--__roundnessTop, --panelRadius) - 1px);
border-top-right-radius: calc(var(--__roundnessTop, --panelRadius) - 1px);
border-bottom-left-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px);
border-bottom-right-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px);
background-color: var(--profileBg);
z-index: -2;
@ -72,21 +74,33 @@
}
}
// Modifiers
&-rounded-t {
&.-rounded-t {
border-top-left-radius: $fallback--panelRadius;
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
--__roundnessTop: var(--panelRadius);
--__roundnessBottom: 0;
}
&-rounded {
&.-rounded {
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
--__roundnessTop: var(--panelRadius);
--__roundnessBottom: var(--panelRadius);
}
&.-popover {
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
--__roundnessTop: var(--tooltipRadius);
--__roundnessBottom: var(--tooltipRadius);
}
&-bordered {
&.-bordered {
border-width: 1px;
border-style: solid;
border-color: $fallback--border;
@ -99,6 +113,15 @@
color: var(--lightText, $fallback--lightText);
padding: 0 26px;
a {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
&:hover {
color: var(--icon);
}
}
.container {
min-width: 0;
padding: 16px 0 6px;
@ -110,23 +133,27 @@
min-width: 0;
}
> a {
vertical-align: middle;
display: flex;
}
.Avatar {
--_avatarShadowBox: var(--avatarShadow);
--_avatarShadowFilter: var(--avatarShadowFilter);
--_avatarShadowInset: var(--avatarShadowInset);
flex: 1 0 100%;
width: 56px;
height: 56px;
object-fit: cover;
}
}
&-avatar-link {
&-avatar {
position: relative;
cursor: pointer;
&-overlay {
&.-overlay {
position: absolute;
left: 0;
top: 0;
@ -146,7 +173,7 @@
}
}
&:hover &-overlay {
&:hover &.-overlay {
opacity: 1;
}
}
@ -206,8 +233,6 @@
flex: 0 1 auto;
text-overflow: ellipsis;
overflow: hidden;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
.dailyAvg {

@ -8,11 +8,11 @@
:style="style"
class="background-image"
/>
<div class="panel-heading -flexible-height">
<div :class="onClose ? '' : panel-heading -flexible-height">
<div class="user-info">
<div class="container">
<a
v-if="allowZoomingAvatar"
v-if="avatarAction === 'zoom'"
class="user-info-avatar -link"
@click="zoomAvatar"
>
@ -27,6 +27,13 @@
/>
</div>
</a>
<UserAvatar
v-else-if="typeof avatarAction === 'function'"
@click="avatarAction"
class="user-info-avatar"
:better-shadow="betterShadow"
:user="user"
/>
<router-link
v-else
:to="userProfileLink(user)"
@ -38,12 +45,16 @@
</router-link>
<div class="user-summary">
<div class="top-line">
<router-link
:to="userProfileLink(user)"
class="user-name"
>
<RichContent
:title="user.name"
class="user-name"
:html="user.name"
:emoji="user.emoji"
/>
</router-link>
<button
v-if="!isOtherUser && user.is_local"
class="button-unstyled edit-profile-button"
@ -72,6 +83,27 @@
:user="user"
:relationship="relationship"
/>
<router-link
v-if="onClose"
:to="userProfileLink(user)"
class="button-unstyled external-link-button"
@click="onClose"
>
<FAIcon
class="icon"
icon="expand-alt"
/>
</router-link>
<button
v-if="onClose"
class="button-unstyled external-link-button"
@click="onClose"
>
<FAIcon
class="icon"
icon="times"
/>
</button>
</div>
<div class="bottom-line">
<router-link

@ -0,0 +1,23 @@
import UserCard from '../user_card/user_card.vue'
import { defineAsyncComponent } from 'vue'
const UserPopover = {
name: 'UserPopover',
props: [
'userId', 'overlayCenters', 'disabled', 'overlayCentersSelector'
],
components: {
UserCard,
Popover: defineAsyncComponent(() => import('../popover/popover.vue'))
},
computed: {
userPopoverZoom () {
return this.$store.getters.mergedConfig.userPopoverZoom
},
userPopoverOverlay () {
return this.$store.getters.mergedConfig.userPopoverOverlay
}
}
}
export default UserPopover

@ -0,0 +1,33 @@
<template>
<Popover
trigger="click"
popover-class="popover-default user-popover"
:overlay-centers-selector="overlayCentersSelector || '.user-info .Avatar'"
:overlay-centers="overlayCenters && userPopoverOverlay"
:disabled="disabled"
>
<template v-slot:trigger>
<slot />
</template>
<template v-slot:content={close}>
<UserCard
class="user-popover"
:user-id="userId"
:hide-bio="true"
:avatar-action="userPopoverZoom ? 'zoom' : close"
:on-close="close"
/>
</template>
</Popover>
</template>
<script src="./user_popover.js" ></script>
<style lang="scss">
@import '../../_variables.scss';
/* popover styles load on-demand, so we need to override */
.user-popover.popover {
}
</style>

@ -8,7 +8,7 @@
:user-id="userId"
:switcher="true"
:selected="timeline.viewing"
:allow-zooming-avatar="true"
:avatar-action="zoom"
rounded="top"
/>
<div

@ -550,6 +550,8 @@
"mention_link_show_avatar": "Show user avatar beside the link",
"mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)",
"mention_link_bolden_you": "Highlight mention of you when you are mentioned",
"user_popover_avatar_zoom": "Clicking on user avatar in popover zooms it instead of closing the popover",
"user_popover_avatar_overlay": "Show user popover over user avatar",
"fun": "Fun",
"greentext": "Meme arrows",
"show_yous": "Show (You)s",

@ -81,6 +81,8 @@ export const defaultState = {
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
userPopoverZoom: false,
userPopoverOverlay: true,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default

@ -356,7 +356,6 @@ describe('RichContent', () => {
p(
'<span class="MentionsLine">',
'<span class="MentionLink mention-link">',
'<!-- eslint-disable vue/no-v-html -->',
'<a href="lol" class="original" target="_blank">',
'<span>',
'https://</span>',
@ -365,10 +364,7 @@ describe('RichContent', () => {
'<span>',
'</span>',
'</a>',
'<!-- eslint-enable vue/no-v-html -->',
'<!---->', // vue placeholder
'</span>',
'<!--v-if-->', // vue placeholder, mentionsline's extra mentions and stuff
'</span>'
),
p(
@ -387,7 +383,7 @@ describe('RichContent', () => {
}
})
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
expect(wrapper.html().replace(/\n/g, '').replace(/<!--.*?-->/g, '')).to.eql(compwrap(expected))
})
it('rich contents of nested mentions are handled properly', () => {
@ -419,7 +415,6 @@ describe('RichContent', () => {
'<span class="poast-style">',
'<span class="MentionsLine">',
'<span class="MentionLink mention-link">',
'<!-- eslint-disable vue/no-v-html -->',
'<a href="lol" class="original" target="_blank">',
'<span>',
'https://</span>',
@ -428,11 +423,8 @@ describe('RichContent', () => {
'<span>',
'</span>',
'</a>',
'<!-- eslint-enable vue/no-v-html -->',
'<!---->', // vue placeholder, mentionlink's "new" (i.e. rich) display
'</span>',
'<span class="MentionLink mention-link">',
'<!-- eslint-disable vue/no-v-html -->',
'<a href="lol" class="original" target="_blank">',
'<span>',
'https://</span>',
@ -441,10 +433,7 @@ describe('RichContent', () => {
'<span>',
'</span>',
'</a>',
'<!-- eslint-enable vue/no-v-html -->',
'<!---->', // vue placeholder, mentionlink's "new" (i.e. rich) display
'</span>',
'<!--v-if-->', // vue placeholder, mentionsline's extra mentions and stuff
'</span>',
' ',
'</span>',
@ -462,7 +451,7 @@ describe('RichContent', () => {
}
})
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
expect(wrapper.html().replace(/\n/g, '').replace(/<!--.*?-->/g, '')).to.eql(compwrap(expected))
})
it('rich contents of a link are handled properly', () => {

Loading…
Cancel
Save