diff options
Diffstat (limited to 'src/components')
110 files changed, 849 insertions, 523 deletions
diff --git a/src/components/about/about.vue b/src/components/about/about.vue index 5d5d6479..33586c97 100644 --- a/src/components/about/about.vue +++ b/src/components/about/about.vue @@ -8,7 +8,7 @@ </div> </template> -<script src="./about.js" ></script> +<script src="./about.js"></script> <style lang="scss"> </style> diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index c35d01af..23547f2c 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -6,7 +6,7 @@ :bound-to="{ x: 'container' }" remove-padding > - <template v-slot:content> + <template #content> <div class="dropdown-menu"> <template v-if="relationship.following"> <button @@ -57,7 +57,7 @@ </button> </div> </template> - <template v-slot:trigger> + <template #trigger> <button class="button-unstyled ellipsis-button"> <FAIcon class="icon" diff --git a/src/components/avatar_list/avatar_list.vue b/src/components/avatar_list/avatar_list.vue index e1b6e971..9a6ca3f6 100644 --- a/src/components/avatar_list/avatar_list.vue +++ b/src/components/avatar_list/avatar_list.vue @@ -14,7 +14,7 @@ </div> </template> -<script src="./avatar_list.js" ></script> +<script src="./avatar_list.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js index 8f41e2fb..8b1a2c38 100644 --- a/src/components/basic_user_card/basic_user_card.js +++ b/src/components/basic_user_card/basic_user_card.js @@ -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) } diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index eeca7828..9cca7840 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -1,24 +1,22 @@ <template> <div class="basic-user-card"> - <router-link :to="userProfileLink(user)"> - <UserAvatar - class="avatar" - :user="user" - @click.prevent="toggleUserExpanded" - /> - </router-link> - <div - v-if="userExpanded" - class="basic-user-card-expanded-content" + <router-link + :to="userProfileLink(user)" + @click.prevent > - <UserCard + <UserPopover :user-id="user.id" - :rounded="true" - :bordered="true" - /> - </div> + :overlay-centers="true" + overlay-centers-selector=".avatar" + > + <UserAvatar + class="user-avatar avatar" + :user="user" + @click.prevent + /> + </UserPopover> + </router-link> <div - v-else class="basic-user-card-collapsed-content" > <div @@ -53,6 +51,8 @@ margin: 0; padding: 0.6em 1em; + --emoji-size: 14px; + &-collapsed-content { margin-left: 0.7em; text-align: left; diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 9f6e64e3..5a5c37b6 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -107,7 +107,7 @@ const Chat = { } }) }, - '$route': function () { + $route: function () { this.startFetching() }, mastoUserSocketStatus (newValue) { diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue index 58e8d0b3..1248c4c8 100644 --- a/src/components/chat_list/chat_list.vue +++ b/src/components/chat_list/chat_list.vue @@ -23,7 +23,7 @@ class="timeline" > <List :items="sortedChatList"> - <template v-slot:item="{item}"> + <template #item="{item}"> <ChatListItem :key="item.id" :compact="false" diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index 5bac7736..ebe09814 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -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' }, diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue index d62b831d..d635c47e 100644 --- a/src/components/chat_message/chat_message.vue +++ b/src/components/chat_message/chat_message.vue @@ -14,16 +14,16 @@ v-if="!isCurrentUser" class="avatar-wrapper" > - <router-link + <UserPopover v-if="chatViewItem.isHead" - :to="userProfileLink" + :user-id="author.id" > <UserAvatar :compact="true" :better-shadow="betterShadow" :user="author" /> - </router-link> + </UserPopover> </div> <div class="chat-message-inner"> <div @@ -44,13 +44,13 @@ <Popover trigger="click" placement="top" - :bound-to-selector="isCurrentUser ? '' : '.scrollable-message-list'" + bound-to-selector=".chat-view-inner" :bound-to="{ x: 'container' }" :margin="popoverMarginStyle" @show="menuOpened = true" @close="menuOpened = false" > - <template v-slot:content> + <template #content> <div class="dropdown-menu"> <button class="button-default dropdown-item dropdown-item-icon" @@ -60,7 +60,7 @@ </button> </div> </template> - <template v-slot:trigger> + <template #trigger> <button class="button-default menu-icon" :title="$t('chats.more')" @@ -75,7 +75,7 @@ :status="messageForStatusContent" :full-content="true" > - <template v-slot:footer> + <template #footer> <span class="created-at" > @@ -96,7 +96,7 @@ </div> </template> -<script src="./chat_message.js" ></script> +<script src="./chat_message.js"></script> <style lang="scss"> @import './chat_message.scss'; diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js index f6e299ad..b8721126 100644 --- a/src/components/chat_title/chat_title.js +++ b/src/components/chat_title/chat_title.js @@ -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) - } } } diff --git a/src/components/chat_title/chat_title.vue b/src/components/chat_title/chat_title.vue index 7f6aaaa4..ab7491fa 100644 --- a/src/components/chat_title/chat_title.vue +++ b/src/components/chat_title/chat_title.vue @@ -3,16 +3,16 @@ class="chat-title" :title="title" > - <router-link - class="avatar-container" + <UserPopover v-if="withAvatar && user" - :to="getUserProfileLink(user)" + class="avatar-container" + :user-id="user.id" > <UserAvatar class="titlebar-avatar" :user="user" /> - </router-link> + </UserPopover> <RichContent v-if="user" class="username" diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 83695912..b6768d67 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -22,12 +22,12 @@ <script> export default { - emits: ['update:modelValue'], props: [ 'modelValue', 'indeterminate', 'disabled' - ] + ], + emits: ['update:modelValue'] } </script> diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index e84603c3..dfc084f9 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -46,7 +46,6 @@ </div> </div> </template> -<style lang="scss" src="./color_input.scss"></style> <script> import Checkbox from '../checkbox/checkbox.vue' import { hex2rgb } from '../../services/color_convert/color_convert.js' @@ -108,6 +107,7 @@ export default { } } </script> +<style lang="scss" src="./color_input.scss"></style> <style lang="scss"> .color-control { diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 2ef2977a..3b540cac 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -271,7 +271,7 @@ const conversation = { result[irid] = result[irid] || [] result[irid].push({ name: `#${i}`, - id: id + id }) } i++ diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 6088e1ca..1adbe250 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -31,8 +31,8 @@ keypath="status.show_all_conversation_with_icon" tag="button" class="button-unstyled -link" - @click.prevent="diveToTopLevel" scope="global" + @click.prevent="diveToTopLevel" > <template #icon> <FAIcon diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index e048f53d..08c0e44e 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -46,23 +46,27 @@ export default { enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, logoStyle () { return { - 'visibility': this.enableMask ? 'hidden' : 'visible' + visibility: this.enableMask ? 'hidden' : 'visible' } }, logoMaskStyle () { - return this.enableMask ? { - 'mask-image': `url(${this.$store.state.instance.logo})` - } : { - 'background-color': this.enableMask ? '' : 'transparent' - } + return this.enableMask + ? { + 'mask-image': `url(${this.$store.state.instance.logo})` + } + : { + 'background-color': this.enableMask ? '' : 'transparent' + } }, logoBgStyle () { return Object.assign({ - 'margin': `${this.$store.state.instance.logoMargin} 0`, + margin: `${this.$store.state.instance.logoMargin} 0`, opacity: this.searchBarHidden ? 1 : 0 - }, this.enableMask ? {} : { - 'background-color': this.enableMask ? '' : 'transparent' - }) + }, this.enableMask + ? {} + : { + 'background-color': this.enableMask ? '' : 'transparent' + }) }, logo () { return this.$store.state.instance.logo }, sitename () { return this.$store.state.instance.name }, diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss index eddd9707..71202244 100644 --- a/src/components/desktop_nav/desktop_nav.scss +++ b/src/components/desktop_nav/desktop_nav.scss @@ -2,6 +2,7 @@ .DesktopNav { width: 100%; + z-index: var(--ZI_navbar); input { color: var(--inputTopbarText, var(--inputText)); diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index bab3ca81..f352c78c 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -38,7 +38,7 @@ /> <button class="button-unstyled nav-icon" - @click.stop="openSettingsModal" + @click="openSettingsModal" > <FAIcon fixed-width diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue index 836688aa..28c61631 100644 --- a/src/components/domain_mute_card/domain_mute_card.vue +++ b/src/components/domain_mute_card/domain_mute_card.vue @@ -9,7 +9,7 @@ class="btn button-default" > {{ $t('domain_mute_card.unmute') }} - <template v-slot:progress> + <template #progress> {{ $t('domain_mute_card.unmute_progress') }} </template> </ProgressButton> @@ -19,7 +19,7 @@ class="btn button-default" > {{ $t('domain_mute_card.mute') }} - <template v-slot:progress> + <template #progress> {{ $t('domain_mute_card.mute_progress') }} </template> </ProgressButton> diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 391cc5b5..5ba3907f 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -321,7 +321,7 @@ const EmojiInput = { } }, scrollIntoView () { - const rootRef = this.$refs['picker'].$el + const rootRef = this.$refs.picker.$el /* Scroller is either `window` (replies in TL), sidebar (main post form, * replies in notifs) or mobile post form. Note that getting and setting * scroll is different for `Window` and `Element`s diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index bd5c2e39..f6920208 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -25,7 +25,7 @@ const filterByKeyword = (list, keyword = '') => { if (keyword === '') return list const keywordLowercase = keyword.toLowerCase() - let orderedEmojiList = [] + const orderedEmojiList = [] for (const emoji of list) { const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase) if (indexOfKeyword > -1) { diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index 2055e02e..a2f17c51 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -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; diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index 51d50359..4ea8b6a2 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -26,7 +26,7 @@ </div> </template> -<script src="./emoji_reactions.js" ></script> +<script src="./emoji_reactions.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index dd45b6b9..22ffb65a 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -25,7 +25,7 @@ library.add( ) const ExtraButtons = { - props: [ 'status' ], + props: ['status'], components: { Popover }, methods: { deleteStatus () { @@ -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}` } diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index a3c3c767..2c893bf3 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -7,7 +7,7 @@ :bound-to="{ x: 'container' }" remove-padding > - <template v-slot:content="{close}"> + <template #content="{close}"> <div class="dropdown-menu"> <button v-if="canMute && !status.thread_muted" @@ -51,28 +51,30 @@ icon="thumbtack" /><span>{{ $t("status.unpin") }}</span> </button> - <button - v-if="!status.bookmarked" - class="button-default dropdown-item dropdown-item-icon" - @click.prevent="bookmarkStatus" - @click="close" - > - <FAIcon - fixed-width - :icon="['far', 'bookmark']" - /><span>{{ $t("status.bookmark") }}</span> - </button> - <button - v-if="status.bookmarked" - class="button-default dropdown-item dropdown-item-icon" - @click.prevent="unbookmarkStatus" - @click="close" - > - <FAIcon - fixed-width - icon="bookmark" - /><span>{{ $t("status.unbookmark") }}</span> - </button> + <template v-if="canBookmark"> + <button + v-if="!status.bookmarked" + class="button-default dropdown-item dropdown-item-icon" + @click.prevent="bookmarkStatus" + @click="close" + > + <FAIcon + fixed-width + :icon="['far', 'bookmark']" + /><span>{{ $t("status.bookmark") }}</span> + </button> + <button + v-if="status.bookmarked" + class="button-default dropdown-item dropdown-item-icon" + @click.prevent="unbookmarkStatus" + @click="close" + > + <FAIcon + fixed-width + icon="bookmark" + /><span>{{ $t("status.unbookmark") }}</span> + </button> + </template> <button v-if="canDelete" class="button-default dropdown-item dropdown-item-icon" @@ -118,18 +120,18 @@ </button> </div> </template> - <template v-slot:trigger> - <button class="button-unstyled popover-trigger"> + <template #trigger> + <span class="button-unstyled popover-trigger"> <FAIcon class="fa-scale-110 fa-old-padding" icon="ellipsis-h" /> - </button> + </span> </template> </Popover> </template> -<script src="./extra_buttons.js" ></script> +<script src="./extra_buttons.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue index dce25e24..d5c4c61e 100644 --- a/src/components/favorite_button/favorite_button.vue +++ b/src/components/favorite_button/favorite_button.vue @@ -29,7 +29,7 @@ </div> </template> -<script src="./favorite_button.js" ></script> +<script src="./favorite_button.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/features_panel/features_panel.vue b/src/components/features_panel/features_panel.vue index a58a99af..4cdf56d0 100644 --- a/src/components/features_panel/features_panel.vue +++ b/src/components/features_panel/features_panel.vue @@ -32,7 +32,7 @@ </div> </template> -<script src="./features_panel.js" ></script> +<script src="./features_panel.js"></script> <style lang="scss"> .features-panel li { diff --git a/src/components/flash/flash.js b/src/components/flash/flash.js index 87f940a7..87c1d650 100644 --- a/src/components/flash/flash.js +++ b/src/components/flash/flash.js @@ -11,7 +11,7 @@ library.add( ) const Flash = { - props: [ 'src' ], + props: ['src'], data () { return { player: false, // can be true, "hidden", false. hidden = element exists diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue index f100c3a9..83c1cef7 100644 --- a/src/components/font_control/font_control.vue +++ b/src/components/font_control/font_control.vue @@ -47,7 +47,7 @@ </div> </template> -<script src="./font_control.js" ></script> +<script src="./font_control.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue index ddc45b81..09904761 100644 --- a/src/components/global_notice_list/global_notice_list.vue +++ b/src/components/global_notice_list/global_notice_list.vue @@ -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; diff --git a/src/components/hashtag_link/hashtag_link.vue b/src/components/hashtag_link/hashtag_link.vue index 918ed26b..596851b9 100644 --- a/src/components/hashtag_link/hashtag_link.vue +++ b/src/components/hashtag_link/hashtag_link.vue @@ -14,6 +14,6 @@ </span> </template> -<script src="./hashtag_link.js"/> +<script src="./hashtag_link.js" /> -<style lang="scss" src="./hashtag_link.scss"/> +<style lang="scss" src="./hashtag_link.scss" /> diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js index 05f6fd4c..55e901a0 100644 --- a/src/components/image_cropper/image_cropper.js +++ b/src/components/image_cropper/image_cropper.js @@ -95,7 +95,7 @@ const ImageCropper = { const fileInput = this.$refs.input if (fileInput.files != null && fileInput.files[0] != null) { this.file = fileInput.files[0] - let reader = new window.FileReader() + const reader = new window.FileReader() reader.onload = (e) => { this.dataUrl = e.target.result this.$emit('open') diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue index 7448ca06..c8ed0a2d 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.vue +++ b/src/components/instance_specific_panel/instance_specific_panel.vue @@ -10,4 +10,4 @@ </div> </template> -<script src="./instance_specific_panel.js" ></script> +<script src="./instance_specific_panel.js"></script> diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js index 95087eac..cc51b470 100644 --- a/src/components/interactions/interactions.js +++ b/src/components/interactions/interactions.js @@ -14,7 +14,7 @@ const Interactions = { data () { return { allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, - filterMode: tabModeDict['mentions'], + filterMode: tabModeDict.mentions, canSeeReports: ['moderator', 'admin'].includes(this.$store.state.users.currentUser.role) } }, diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index 7ad1fe2e..6997f149 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -25,6 +25,7 @@ import Select from '../select/select.vue' export default { components: { + // eslint-disable-next-line vue/no-reserved-component-names Select }, props: { diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 638bd812..b795640e 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -83,7 +83,7 @@ const LoginForm = { }, clearError () { this.error = false }, focusOnPasswordInput () { - let passwordInput = this.$refs.passwordInput + const passwordInput = this.$refs.passwordInput passwordInput.focus() passwordInput.setSelectionRange(0, passwordInput.value.length) } diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index 21482977..7a430c51 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -90,7 +90,7 @@ </div> </template> -<script src="./login_form.js" ></script> +<script src="./login_form.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 8b76aafb..d59055b3 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -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, diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js index 669d8190..cfd42d4c 100644 --- a/src/components/media_upload/media_upload.js +++ b/src/components/media_upload/media_upload.js @@ -42,7 +42,8 @@ const mediaUpload = { .then((fileData) => { self.$emit('uploaded', fileData) self.decreaseUploadCount() - }, (error) => { // eslint-disable-line handle-callback-err + }, (error) => { + console.error('Error uploading file', error) self.$emit('upload-failed', 'default') self.decreaseUploadCount() }) @@ -73,7 +74,7 @@ const mediaUpload = { 'disabled' ], watch: { - 'dropFiles': function (fileInfos) { + dropFiles: function (fileInfos) { if (!this.uploading) { this.multiUpload(fileInfos) } diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue index 7cc59f5a..a538a5ed 100644 --- a/src/components/media_upload/media_upload.vue +++ b/src/components/media_upload/media_upload.vue @@ -26,7 +26,7 @@ </label> </template> -<script src="./media_upload.js" ></script> +<script src="./media_upload.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 55eea195..4a74fbe2 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -2,6 +2,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p import { mapGetters, mapState } from 'vuex' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import UserAvatar from '../user_avatar/user_avatar.vue' +import { defineAsyncComponent } from 'vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faAt @@ -14,7 +15,8 @@ library.add( const MentionLink = { name: 'MentionLink', components: { - UserAvatar + UserAvatar, + UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue')) }, props: { url: { @@ -34,15 +36,30 @@ const MentionLink = { type: String } }, + data () { + return { + hasSelection: false + } + }, methods: { onClick () { + if (this.shouldShowTooltip) return const link = generateProfileLink( this.userId || this.user.id, 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) @@ -88,7 +105,8 @@ const MentionLink = { return [ { '-you': this.isYou && this.shouldBoldenYou, - '-highlighted': this.highlight + '-highlighted': this.highlight, + '-has-selection': this.hasSelection }, this.highlightType ] @@ -110,7 +128,7 @@ const MentionLink = { } }, shouldShowTooltip () { - return this.mergedConfig.mentionLinkShowTooltip && this.mergedConfig.mentionLinkDisplay === 'short' && this.isRemote + return this.mergedConfig.mentionLinkShowTooltip }, shouldShowAvatar () { return this.mergedConfig.mentionLinkShowAvatar diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss index 1d856ff9..8b2af926 100644 --- a/src/components/mention_link/mention_link.scss +++ b/src/components/mention_link/mention_link.scss @@ -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,30 +88,29 @@ } &.-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 { - opacity: 1; - pointer-events: initial; + .full { + pointer-events: none; } .serverName.-faded { color: var(--faintLink, $fallback--link); } +} - .full .-faded { - color: var(--faint, $fallback--faint); - } +.mention-link-popover { + max-width: 70ch; + max-height: 20rem; + overflow: hidden; } diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue index 022f04c7..3af502ef 100644 --- a/src/components/mention_link/mention_link.vue +++ b/src/components/mention_link/mention_link.vue @@ -9,69 +9,64 @@ class="original" target="_blank" v-html="content" - /><!-- eslint-enable vue/no-v-html --><span - v-if="user" - class="new" - :style="style" - :class="classnames" + /><!-- eslint-enable vue/no-v-html --> + <UserPopover + v-else + :user-id="user.id" + :disabled="!shouldShowTooltip" > - <a - class="short button-unstyled" - :class="{ '-with-tooltip': shouldShowTooltip }" - :href="url" - @click.prevent="onClick" + <span + v-if="user" + class="new" + :style="style" + :class="classnames" > - <!-- eslint-disable vue/no-v-html --> - <UserAvatar - v-if="shouldShowAvatar" - class="mention-avatar" - :user="user" - /><span - class="shortName" - ><FAIcon - v-if="useAtIcon" - size="sm" - icon="at" - class="at" - />{{ !useAtIcon ? '@' : '' }}<span - class="userName" - v-html="userName" - /><span - v-if="shouldShowFullUserName" - class="serverName" - :class="{ '-faded': shouldFadeDomain }" - v-html="'@' + serverName" - /> - </span> - <span - v-if="isYou && shouldShowYous" - :class="{ '-you': shouldBoldenYou }" - > {{ ' ' + $t('status.you') }}</span> - <!-- eslint-enable vue/no-v-html --> - </a><span - v-if="shouldShowTooltip" - class="full popover-default" - :class="[highlightType]" - > - <span - class="userNameFull" + <a + class="short button-unstyled" + :class="{ '-with-tooltip': shouldShowTooltip }" + :href="url" + @click.prevent="onClick" > <!-- eslint-disable vue/no-v-html --> - @<span + <UserAvatar + v-if="shouldShowAvatar" + class="mention-avatar" + :user="user" + /><span + class="shortName" + ><FAIcon + v-if="useAtIcon" + size="sm" + icon="at" + class="at" + />{{ !useAtIcon ? '@' : '' }}<span class="userName" v-html="userName" /><span + v-if="shouldShowFullUserName" class="serverName" :class="{ '-faded': shouldFadeDomain }" v-html="'@' + serverName" /> + </span> + <span + v-if="isYou && shouldShowYous" + :class="{ '-you': shouldBoldenYou }" + > {{ ' ' + $t('status.you') }}</span> + <!-- eslint-enable vue/no-v-html --> + </a><span + ref="full" + class="full" + > + <!-- eslint-disable vue/no-v-html --> + @<span v-html="userName" /><span v-html="'@' + serverName" /> <!-- eslint-enable vue/no-v-html --> </span> </span> - </span> + </UserPopover> </span> </template> -<script src="./mention_link.js"/> +<script src="./mention_link.js" /> -<style lang="scss" src="./mention_link.scss"/> +<style lang="scss" src="./mention_link.scss" /> diff --git a/src/components/mentions_line/mentions_line.vue b/src/components/mentions_line/mentions_line.vue index 09b6a1d6..64c19bf1 100644 --- a/src/components/mentions_line/mentions_line.vue +++ b/src/components/mentions_line/mentions_line.vue @@ -13,14 +13,13 @@ <span v-if="expanded" class="fullExtraMentions" - > - <MentionLink - v-for="mention in extraMentions" - :key="mention.index" - class="mention-link" - :content="mention.content" - :url="mention.url" - /> + >{{ ' ' }}<MentionLink + v-for="mention in extraMentions" + :key="mention.index" + class="mention-link" + :content="mention.content" + :url="mention.url" + /> </span><button v-if="!expanded" class="button-unstyled showMoreLess" @@ -37,5 +36,5 @@ </span> </span> </template> -<script src="./mentions_line.js" ></script> +<script src="./mentions_line.js"></script> <style lang="scss" src="./mentions_line.scss" /> diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue index a9cf39aa..5988fa51 100644 --- a/src/components/mfa_form/recovery_form.vue +++ b/src/components/mfa_form/recovery_form.vue @@ -69,4 +69,4 @@ </div> </div> </template> -<script src="./recovery_form.js" ></script> +<script src="./recovery_form.js"></script> diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index d2d48a03..949cf17e 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -67,11 +67,10 @@ </a> </div> <div - class="mobile-notifications" id="mobile-notifications" + class="mobile-notifications" @scroll="onScroll" - > - </div> + /> </div> <SideDrawer ref="sideDrawer" @@ -86,6 +85,8 @@ @import '../../_variables.scss'; .MobileNav { + z-index: var(--ZI_navbar); + .mobile-nav { display: grid; line-height: var(--navbar-height); @@ -147,7 +148,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 +161,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; diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue index 9394efff..2187f392 100644 --- a/src/components/modal/modal.vue +++ b/src/components/modal/modal.vue @@ -12,6 +12,9 @@ <script> export default { + provide: { + popoversZLayer: 'modals' + }, props: { isOpen: { type: Boolean, @@ -26,7 +29,7 @@ export default { classes () { return { 'modal-background': !this.noBackground, - 'open': this.isOpen + open: this.isOpen } } } @@ -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; diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue index 96b8c3a3..34fe2e7c 100644 --- a/src/components/moderation_tools/moderation_tools.vue +++ b/src/components/moderation_tools/moderation_tools.vue @@ -8,7 +8,7 @@ @show="setToggled(true)" @close="setToggled(false)" > - <template v-slot:content> + <template #content> <div class="dropdown-menu"> <span v-if="user.is_local"> <button @@ -122,7 +122,7 @@ </span> </div> </template> - <template v-slot:trigger> + <template #trigger> <button class="btn button-default btn-block moderation-tools-button" :class="{ toggled }" @@ -137,11 +137,11 @@ v-if="showDeleteUserDialog" :on-cancel="deleteUserDialog.bind(this, false)" > - <template v-slot:header> + <template #header> {{ $t('user_card.admin_menu.delete_user') }} </template> <p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p> - <template v-slot:footer> + <template #footer> <button class="btn button-default" @click="deleteUserDialog(false)" diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js index 3fde8106..13cfb52e 100644 --- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js +++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js @@ -9,10 +9,10 @@ import { get } from 'lodash' */ const toInstanceReasonObject = (instances, info, key) => { return instances.map(instance => { - if (info[key] && info[key][instance] && info[key][instance]['reason']) { - return { instance: instance, reason: info[key][instance]['reason'] } + if (info[key] && info[key][instance] && info[key][instance].reason) { + return { instance, reason: info[key][instance].reason } } - return { instance: instance, reason: '' } + return { instance, reason: '' } }) } diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 7ae7b1d6..3fd27d89 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -90,7 +90,7 @@ </div> </template> -<script src="./nav_panel.js" ></script> +<script src="./nav_panel.js"></script> <style lang="scss"> @import '../../_variables.scss'; @@ -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); diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 8f74c0e6..f60a1a6a 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -6,6 +6,7 @@ import UserCard from '../user_card/user_card.vue' import Timeago from '../timeago/timeago.vue' import Report from '../report/report.vue' import RichContent from 'src/components/rich_content/rich_content.jsx' +import UserPopover from '../user_popover/user_popover.vue' import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -40,7 +41,7 @@ const Notification = { unmuted: false } }, - props: [ 'notification' ], + props: ['notification'], components: { StatusContent, UserAvatar, @@ -48,7 +49,8 @@ const Notification = { Timeago, Status, Report, - RichContent + RichContent, + UserPopover }, methods: { toggleUserExpanded () { diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index ebbe5f95..6a5417b3 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -34,21 +34,22 @@ <a class="avatar-container" :href="$router.resolve(userProfileLink).href" - @click.stop.prevent.capture="toggleUserExpanded" + @click.prevent > - <UserAvatar - :compact="true" - :better-shadow="betterShadow" - :user="notification.from_profile" - /> + <UserPopover + :user-id="notification.from_profile.id" + :overlay-centers="true" + > + <UserAvatar + class="post-avatar" + :bot="botIndicator" + :compact="true" + :better-shadow="betterShadow" + :user="notification.from_profile" + /> + </UserPopover> </a> <div class="notification-right"> - <UserCard - v-if="userExpanded" - :user-id="getUser(notification).id" - :rounded="true" - :bordered="true" - /> <span class="notification-details"> <div class="name-and-action"> <!-- eslint-disable vue/no-v-html --> diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue index 00a531b3..b0213167 100644 --- a/src/components/notifications/notification_filters.vue +++ b/src/components/notifications/notification_filters.vue @@ -5,7 +5,7 @@ placement="bottom" :bound-to="{ x: 'container' }" > - <template v-slot:content> + <template #content> <div class="dropdown-menu"> <button class="button-default dropdown-item" @@ -72,7 +72,7 @@ </button> </div> </template> - <template v-slot:trigger> + <template #trigger> <button class="filter-trigger-button button-unstyled"> <FAIcon icon="filter" /> </button> diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 82aa1489..0851f407 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -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) }, diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index b46c06aa..e778e27b 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -1,5 +1,8 @@ <template> - <teleport :disabled="minimalMode || disableTeleport" :to="teleportTarget"> + <teleport + :disabled="minimalMode || disableTeleport" + :to="teleportTarget" + > <div :class="{ minimal: minimalMode }" class="Notifications" diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue index f269d60e..146754db 100644 --- a/src/components/poll/poll_form.vue +++ b/src/components/poll/poll_form.vue @@ -84,7 +84,7 @@ :key="unit" :value="unit" > - {{ $t(`time.${unit}_short`, ['']) }} + {{ $tc(`time.unit.${unit}_short`, expiryAmount, ['']) }} </option> </Select> </div> diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index a30a37c9..d2af59fe 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -31,13 +31,35 @@ const Popover = { // If true, subtract padding when calculating position for the popover, // use it when popover offset looks to be different on top vs bottom. - removePadding: Boolean + removePadding: Boolean, + + // self-explanatory (i hope) + disabled: Boolean, + + // Instead of putting popover next to anchor, overlay popover's center on top of anchor's center + overlayCenters: Boolean, + + // What selector (witin popover!) to use for determining center of popover + overlayCentersSelector: String, + + // Lets hover popover stay when clicking inside of it + stayOnClick: Boolean }, + 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: { opacity: 0 }, - oldSize: { width: 0, height: 0 } + styles: {}, + oldSize: { width: 0, height: 0 }, + scrollable: null, + // used to avoid blinking if hovered onto popover + graceTimeout: null, + parentPopover: null, + childrenShown: new Set() } }, methods: { @@ -47,9 +69,7 @@ const Popover = { }, updateStyles () { if (this.hidden) { - this.styles = { - opacity: 0 - } + this.styles = {} return } @@ -57,14 +77,26 @@ const Popover = { // its children are what are inside the slot. Expect only one v-slot:trigger. const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el // SVGs don't have offsetWidth/Height, use fallback - const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight - 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 anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth + const anchorScreenBox = anchorEl.getBoundingClientRect() + + const anchorStyle = getComputedStyle(anchorEl) + const topPadding = parseFloat(anchorStyle.paddingTop) + const bottomPadding = parseFloat(anchorStyle.paddingBottom) + + // Screen position of the origin point for popover = center of the anchor + const origin = { + x: anchorScreenBox.left + anchorWidth * 0.5, + y: anchorScreenBox.top + anchorHeight * 0.5 + } const content = this.$refs.content + const overlayCenter = this.overlayCenters + ? this.$refs.content.querySelector(this.overlayCentersSelector) + : null + // Minor optimization, don't call a slow reflow call if we don't have to - const parentBounds = this.boundTo && + const parentScreenBox = this.boundTo && (this.boundTo.x === 'container' || this.boundTo.y === 'container') && this.containerBoundingClientRect() @@ -72,82 +104,156 @@ const Popover = { // 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 xBounds = this.boundTo && this.boundTo.x === 'container' + ? { + min: parentScreenBox.left + (margin.left || 0), + max: parentScreenBox.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) - } + const yBounds = this.boundTo && this.boundTo.y === 'container' + ? { + min: parentScreenBox.top + (margin.top || 0), + max: parentScreenBox.bottom - (margin.bottom || 0) + } + : { + min: 0 + (margin.top || 50), + max: window.innerHeight - (margin.bottom || 5) + } let horizOffset = 0 + let vertOffset = 0 + + if (overlayCenter) { + const box = content.getBoundingClientRect() + const overlayCenterScreenBox = overlayCenter.getBoundingClientRect() + const leftInnerOffset = overlayCenterScreenBox.left - box.left + const topInnerOffset = overlayCenterScreenBox.top - box.top + horizOffset = -leftInnerOffset - overlayCenter.offsetWidth * 0.5 + vertOffset = -topInnerOffset - overlayCenter.offsetHeight * 0.5 + } else { + horizOffset = content.offsetWidth * -0.5 + vertOffset = content.offsetHeight * -0.5 + } + + const leftBorder = origin.x + horizOffset + const rightBorder = leftBorder + content.offsetWidth + const topBorder = origin.y + vertOffset + const bottomBorder = topBorder + content.offsetHeight // 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 (leftBorder < xBounds.min) { + horizOffset += xBounds.min - leftBorder } // 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 + if (rightBorder > xBounds.max) { + horizOffset -= rightBorder - 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 + // If overflowing from top, move it so that it doesn't + if (topBorder < yBounds.min) { + vertOffset += yBounds.min - topBorder + } - let vPadding = 0 - if (this.removePadding && usingTop) { - const anchorStyle = getComputedStyle(anchorEl) - vPadding = parseFloat(anchorStyle.paddingTop) + parseFloat(anchorStyle.paddingBottom) + // If overflowing from bottom, move it so that it doesn't + if (bottomBorder > yBounds.max) { + vertOffset -= bottomBorder - yBounds.max } - const yOffset = (this.offset && this.offset.y) || 0 - const translateY = usingTop - ? -anchorHeight + vPadding - yOffset - content.offsetHeight - : yOffset + let translateX = 0 + let translateY = 0 + + if (overlayCenter) { + translateX = origin.x + horizOffset + translateY = origin.y + vertOffset + } else { + // Default to whatever user wished with placement prop + let usingTop = this.placement !== 'bottom' - const xOffset = (this.offset && this.offset.x) || 0 - const translateX = anchorWidth * 0.5 - content.offsetWidth * 0.5 + horizOffset + xOffset + // 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. + const topBoundary = origin.y - anchorHeight * 0.5 + (this.removePadding ? topPadding : 0) + const bottomBoundary = origin.y + anchorHeight * 0.5 - (this.removePadding ? bottomPadding : 0) + if (bottomBoundary + content.offsetHeight > yBounds.max) usingTop = true + if (topBoundary - content.offsetHeight < yBounds.min) usingTop = false + + const yOffset = (this.offset && this.offset.y) || 0 + translateY = usingTop + ? topBoundary - yOffset - content.offsetHeight + : bottomBoundary + yOffset + + const xOffset = (this.offset && this.offset.x) || 0 + translateX = origin.x + 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.round(translateX)}px) translateY(${Math.round(translateY)}px)` + left: `${Math.round(translateX)}px`, + 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` } }, showPopover () { + if (this.disabled) return const wasHidden = this.hidden this.hidden = false + this.parentPopover && this.parentPopover.onChildPopoverState(this, true) + if (this.trigger === 'click' || this.stayOnClick) { + 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 - this.styles = { opacity: 0 } + this.parentPopover && this.parentPopover.onChildPopoverState(this, false) + 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.showPopover() + if (this.trigger === 'hover') { + this.lockReEntry = false + clearTimeout(this.graceTimeout) + this.graceTimeout = null + this.showPopover() + } }, onMouseleave (e) { - if (this.trigger === 'hover') this.hidePopover() + if (this.trigger === 'hover' && this.childrenShown.size === 0) { + this.graceTimeout = setTimeout(() => this.hidePopover(), 1) + } + }, + onMouseenterContent (e) { + if (this.trigger === 'hover' && !this.lockReEntry) { + this.lockReEntry = true + clearTimeout(this.graceTimeout) + this.graceTimeout = null + this.showPopover() + } + }, + onMouseleaveContent (e) { + if (this.trigger === 'hover' && this.childrenShown.size === 0) { + this.graceTimeout = setTimeout(() => this.hidePopover(), 1) + } }, onClick (e) { if (this.trigger === 'click') { @@ -160,8 +266,24 @@ const Popover = { }, onClickOutside (e) { if (this.hidden) return + if (this.$refs.content && this.$refs.content.contains(e.target)) return if (this.$el.contains(e.target)) return + if (this.childrenShown.size > 0) return this.hidePopover() + if (this.parentPopover) this.parentPopover.onClickOutside(e) + }, + onScroll (e) { + this.updateStyles() + }, + onResize (e) { + this.updateStyles() + }, + onChildPopoverState (childRef, state) { + if (state) { + this.childrenShown.add(childRef) + } else { + this.childrenShown.delete(childRef) + } } }, updated () { @@ -175,11 +297,18 @@ const Popover = { this.oldSize = { width: content.offsetWidth, height: content.offsetHeight } } }, - created () { - document.addEventListener('click', this.onClickOutside) + mounted () { + let scrollable = this.$refs.trigger.closest('.column.-scrollable') || + this.$refs.trigger.closest('.mobile-notifications') + if (!scrollable) scrollable = window + this.scrollable = scrollable + let parent = this.$parent + while (parent && parent.$.type.name !== 'Popover') { + parent = parent.$parent + } + this.parentPopover = parent }, - unmounted () { - document.removeEventListener('click', this.onClickOutside) + beforeUnmount () { this.hidePopover() } } diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue index c2a3e801..bd59cade 100644 --- a/src/components/popover/popover.vue +++ b/src/components/popover/popover.vue @@ -1,5 +1,5 @@ <template> - <div + <span @mouseenter="onMouseenter" @mouseleave="onMouseleave" > @@ -11,20 +11,27 @@ > <slot name="trigger" /> </button> - <div - v-if="!hidden" - ref="content" - :style="styles" - class="popover" - :class="popoverClass || 'popover-default'" - > - <slot - name="content" - class="popover-inner" - :close="hidePopover" - /> - </div> - </div> + <teleport to="#popovers"> + <transition name="fade"> + <div + v-if="!hidden" + ref="content" + :style="styles" + class="popover" + :class="popoverClass || 'popover-default'" + @mouseenter="onMouseenterContent" + @mouseleave="onMouseleaveContent" + @click="onClickContent" + > + <slot + name="content" + class="popover-inner" + :close="hidePopover" + /> + </div> + </transition> + </teleport> + </span> </template> <script src="./popover.js" /> @@ -37,14 +44,15 @@ } .popover { - z-index: 500; - position: absolute; + z-index: var(--ZI_popover_override, var(--ZI_popovers)); + position: fixed; min-width: 0; + max-width: calc(100vw - 20px); + box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); + box-shadow: var(--popupShadow); } .popover-default { - transition: opacity 0.3s; - &:after { content: ''; position: absolute; @@ -80,7 +88,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 { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 2febf226..c0d80b20 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -41,7 +41,7 @@ const buildMentionsString = ({ user, attentions = [] }, currentUser) => { allAttentions = uniqBy(allAttentions, 'id') allAttentions = reject(allAttentions, { id: currentUser.id }) - let mentions = map(allAttentions, (attention) => { + const mentions = map(allAttentions, (attention) => { return `@${attention.screen_name}` }) @@ -242,7 +242,7 @@ const PostStatusForm = { }) }, watch: { - 'newStatus': { + newStatus: { deep: true, handler () { this.statusChanged() @@ -273,7 +273,7 @@ const PostStatusForm = { this.$refs.textarea.focus() }) } - let el = this.$el.querySelector('textarea') + const el = this.$el.querySelector('textarea') el.style.height = 'auto' el.style.height = undefined this.error = null @@ -392,7 +392,7 @@ const PostStatusForm = { this.$emit('resize', { delayed: true }) }, removeMediaFile (fileInfo) { - let index = this.newStatus.files.indexOf(fileInfo) + const index = this.newStatus.files.indexOf(fileInfo) this.newStatus.files.splice(index, 1) this.$emit('resize') }, @@ -462,7 +462,7 @@ const PostStatusForm = { }, onEmojiInputInput (e) { this.$nextTick(() => { - this.resize(this.$refs['textarea']) + this.resize(this.$refs.textarea) }) }, resize (e) { @@ -477,8 +477,8 @@ const PostStatusForm = { return } - const formRef = this.$refs['form'] - const bottomRef = this.$refs['bottom'] + const formRef = this.$refs.form + const bottomRef = this.$refs.bottom /* Scroller is either `window` (replies in TL), sidebar (main post form, * replies in notifs) or mobile post form. Note that getting and setting * scroll is different for `Window` and `Element`s @@ -564,7 +564,7 @@ const PostStatusForm = { this.$refs['emoji-input'].resize() }, showEmojiPicker () { - this.$refs['textarea'].focus() + this.$refs.textarea.focus() this.$refs['emoji-input'].triggerShowPicker() }, clearError () { diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js index e6f9dbff..37d6e7d0 100644 --- a/src/components/react_button/react_button.js +++ b/src/components/react_button/react_button.js @@ -45,7 +45,7 @@ const ReactButton = { emojis () { if (this.filterWord !== '') { const filterWordLowercase = trim(this.filterWord.toLowerCase()) - let orderedEmojiList = [] + const orderedEmojiList = [] for (const emoji of this.$store.state.instance.emoji) { if (emoji.replacement === this.filterWord) return [emoji] diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue index 8a4b4d3b..5a809847 100644 --- a/src/components/react_button/react_button.vue +++ b/src/components/react_button/react_button.vue @@ -6,15 +6,16 @@ :offset="{ y: 5 }" :bound-to="{ x: 'container' }" remove-padding + popover-class="ReactButton popover-default" @show="focusInput" > - <template v-slot:content="{close}"> + <template #content="{close}"> <div class="reaction-picker-filter"> <input v-model="filterWord" - @input="$event.target.composing = false" size="1" :placeholder="$t('emoji.search_emoji')" + @input="$event.target.composing = false" > </div> <div class="reaction-picker"> @@ -40,8 +41,8 @@ <div class="reaction-bottom-fader" /> </div> </template> - <template v-slot:trigger> - <button + <template #trigger> + <span class="button-unstyled popover-trigger" :title="$t('tool_tip.add_reaction')" > @@ -49,12 +50,12 @@ class="fa-scale-110 fa-old-padding" :icon="['far', 'smile-beam']" /> - </button> + </span> </template> </Popover> </template> -<script src="./react_button.js" ></script> +<script src="./react_button.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index cc655c0b..d78d8da9 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -23,6 +23,7 @@ v-model.trim="v$.user.username.$model" :disabled="isPending" class="form-control" + :aria-required="true" :placeholder="$t('registration.username_placeholder')" > </div> @@ -50,6 +51,7 @@ v-model.trim="v$.user.fullname.$model" :disabled="isPending" class="form-control" + :aria-required="true" :placeholder="$t('registration.fullname_placeholder')" > </div> @@ -71,13 +73,14 @@ <label class="form--label" for="email" - >{{ $t('registration.email') }}</label> + >{{ accountActivationRequired ? $t('registration.email') : $t('registration.email_optional') }}</label> <input id="email" v-model="v$.user.email.$model" :disabled="isPending" class="form-control" type="email" + :aria-required="accountActivationRequired" > </div> <div @@ -95,7 +98,7 @@ <label class="form--label" for="bio" - >{{ $t('registration.bio') }} ({{ $t('general.optional') }})</label> + >{{ $t('registration.bio_optional') }}</label> <textarea id="bio" v-model="user.bio" @@ -119,6 +122,7 @@ :disabled="isPending" class="form-control" type="password" + :aria-required="true" > </div> <div @@ -146,6 +150,7 @@ :disabled="isPending" class="form-control" type="password" + :aria-required="true" > </div> <div diff --git a/src/components/remote_follow/remote_follow.js b/src/components/remote_follow/remote_follow.js index 461d58c9..56b264fc 100644 --- a/src/components/remote_follow/remote_follow.js +++ b/src/components/remote_follow/remote_follow.js @@ -1,5 +1,5 @@ export default { - props: [ 'user' ], + props: ['user'], computed: { subscribeUrl () { // eslint-disable-next-line no-undef diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue index 859ce499..5a15d387 100644 --- a/src/components/retweet_button/retweet_button.vue +++ b/src/components/retweet_button/retweet_button.vue @@ -36,7 +36,7 @@ </div> </template> -<script src="./retweet_button.js" ></script> +<script src="./retweet_button.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js index 551649c7..3b297f09 100644 --- a/src/components/search_bar/search_bar.js +++ b/src/components/search_bar/search_bar.js @@ -16,7 +16,7 @@ const SearchBar = { error: false }), watch: { - '$route': function (route) { + $route: function (route) { if (route.name === 'search') { this.searchTerm = route.query.query } diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue index 3c80660e..1f7683ab 100644 --- a/src/components/selectable_list/selectable_list.vue +++ b/src/components/selectable_list/selectable_list.vue @@ -24,7 +24,7 @@ :items="items" :get-key="getKey" > - <template v-slot:item="{item}"> + <template #item="{item}"> <div class="selectable-list-item-inner" :class="{ 'selectable-list-item-selected-inner': isSelected(item) }" @@ -41,7 +41,7 @@ /> </div> </template> - <template v-slot:empty> + <template #empty> <slot name="empty" /> </template> </List> diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue index ad212db9..8311533a 100644 --- a/src/components/settings_modal/helpers/modified_indicator.vue +++ b/src/components/settings_modal/helpers/modified_indicator.vue @@ -6,14 +6,14 @@ <Popover trigger="hover" > - <template v-slot:trigger> + <template #trigger> <FAIcon icon="wrench" :aria-label="$t('settings.setting_changed')" /> </template> - <template v-slot:content> + <template #content> <div class="modified-tooltip"> {{ $t('settings.setting_changed') }} </div> @@ -41,11 +41,11 @@ export default { .ModifiedIndicator { display: inline-block; position: relative; +} - .modified-tooltip { - margin: 0.5em 1em; - min-width: 10em; - text-align: center; - } +.modified-tooltip { + margin: 0.5em 1em; + min-width: 10em; + text-align: center; } </style> diff --git a/src/components/settings_modal/helpers/server_side_indicator.vue b/src/components/settings_modal/helpers/server_side_indicator.vue index 143a86a1..bf181959 100644 --- a/src/components/settings_modal/helpers/server_side_indicator.vue +++ b/src/components/settings_modal/helpers/server_side_indicator.vue @@ -6,14 +6,14 @@ <Popover trigger="hover" > - <template v-slot:trigger> + <template #trigger> <FAIcon icon="server" :aria-label="$t('settings.setting_server_side')" /> </template> - <template v-slot:content> + <template #content> <div class="serverside-tooltip"> {{ $t('settings.setting_server_side') }} </div> @@ -41,11 +41,11 @@ export default { .ServerSideIndicator { display: inline-block; position: relative; +} - .serverside-tooltip { - margin: 0.5em 1em; - min-width: 10em; - text-align: center; - } +.serverside-tooltip { + margin: 0.5em 1em; + min-width: 10em; + text-align: center; } </style> diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index d3bed061..7b457371 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -53,7 +53,7 @@ :bound-to="{ x: 'container' }" remove-padding > - <template v-slot:trigger> + <template #trigger> <button class="btn button-default" :title="$t('general.close')" @@ -65,7 +65,7 @@ /> </button> </template> - <template v-slot:content="{close}"> + <template #content="{close}"> <div class="dropdown-menu"> <button class="button-default dropdown-item dropdown-item-icon" diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index 1fe51b6d..a2609200 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -75,6 +75,22 @@ </BooleanSetting> </li> <li> + <BooleanSetting + path="userPopoverZoom" + expert="1" + > + {{ $t('settings.user_popover_avatar_zoom') }} + </BooleanSetting> + </li> + <li> + <BooleanSetting + path="userPopoverOverlay" + expert="1" + > + {{ $t('settings.user_popover_avatar_overlay') }} + </BooleanSetting> + </li> + <li> <ChoiceSetting v-if="user" id="thirdColumnMode" @@ -261,18 +277,14 @@ {{ $t('settings.mention_link_display') }} </ChoiceSetting> </li> - <ul - class="setting-list suboptions" - > - <li v-if="mentionLinkDisplay === 'short'"> - <BooleanSetting - path="mentionLinkShowTooltip" - expert="1" - > - {{ $t('settings.mention_link_show_tooltip') }} - </BooleanSetting> - </li> - </ul> + <li> + <BooleanSetting + path="mentionLinkShowTooltip" + expert="1" + > + {{ $t('settings.mention_link_use_tooltip') }} + </BooleanSetting> + </li> <li> <BooleanSetting path="useAtIcon" diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue index 32a21415..c515d542 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue @@ -10,7 +10,7 @@ :query="queryUserIds" :placeholder="$t('settings.search_user_to_block')" > - <template v-slot="row"> + <template #default="row"> <BlockCard :user-id="row.item" /> @@ -21,7 +21,7 @@ :refresh="true" :get-key="i => i" > - <template v-slot:header="{selected}"> + <template #header="{selected}"> <div class="bulk-actions"> <ProgressButton v-if="selected.length > 0" @@ -29,7 +29,7 @@ :click="() => blockUsers(selected)" > {{ $t('user_card.block') }} - <template v-slot:progress> + <template #progress> {{ $t('user_card.block_progress') }} </template> </ProgressButton> @@ -39,16 +39,16 @@ :click="() => unblockUsers(selected)" > {{ $t('user_card.unblock') }} - <template v-slot:progress> + <template #progress> {{ $t('user_card.unblock_progress') }} </template> </ProgressButton> </div> </template> - <template v-slot:item="{item}"> + <template #item="{item}"> <BlockCard :user-id="item" /> </template> - <template v-slot:empty> + <template #empty> {{ $t('settings.no_blocks') }} </template> </BlockList> @@ -63,7 +63,7 @@ :query="queryUserIds" :placeholder="$t('settings.search_user_to_mute')" > - <template v-slot="row"> + <template #default="row"> <MuteCard :user-id="row.item" /> @@ -74,7 +74,7 @@ :refresh="true" :get-key="i => i" > - <template v-slot:header="{selected}"> + <template #header="{selected}"> <div class="bulk-actions"> <ProgressButton v-if="selected.length > 0" @@ -82,7 +82,7 @@ :click="() => muteUsers(selected)" > {{ $t('user_card.mute') }} - <template v-slot:progress> + <template #progress> {{ $t('user_card.mute_progress') }} </template> </ProgressButton> @@ -92,16 +92,16 @@ :click="() => unmuteUsers(selected)" > {{ $t('user_card.unmute') }} - <template v-slot:progress> + <template #progress> {{ $t('user_card.unmute_progress') }} </template> </ProgressButton> </div> </template> - <template v-slot:item="{item}"> + <template #item="{item}"> <MuteCard :user-id="item" /> </template> - <template v-slot:empty> + <template #empty> {{ $t('settings.no_mutes') }} </template> </MuteList> @@ -114,7 +114,7 @@ :query="queryKnownDomains" :placeholder="$t('settings.type_domains_to_mute')" > - <template v-slot="row"> + <template #default="row"> <DomainMuteCard :domain="row.item" /> @@ -125,7 +125,7 @@ :refresh="true" :get-key="i => i" > - <template v-slot:header="{selected}"> + <template #header="{selected}"> <div class="bulk-actions"> <ProgressButton v-if="selected.length > 0" @@ -133,16 +133,16 @@ :click="() => unmuteDomains(selected)" > {{ $t('domain_mute_card.unmute') }} - <template v-slot:progress> + <template #progress> {{ $t('domain_mute_card.unmute_progress') }} </template> </ProgressButton> </div> </template> - <template v-slot:item="{item}"> + <template #item="{item}"> <DomainMuteCard :domain="item" /> </template> - <template v-slot:empty> + <template #empty> {{ $t('settings.no_mutes') }} </template> </DomainMuteList> diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 8781bb91..376248ef 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -71,10 +71,12 @@ const ProfileTab = { }) }, emojiSuggestor () { - return suggestor({ emoji: [ - ...this.$store.state.instance.emoji, - ...this.$store.state.instance.customEmoji - ] }) + return suggestor({ + emoji: [ + ...this.$store.state.instance.emoji, + ...this.$store.state.instance.customEmoji + ] + }) }, userSuggestor () { return suggestor({ store: this.$store }) diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index 4cd93772..642d54ca 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -117,8 +117,8 @@ <button v-if="!isDefaultAvatar && pickAvatarBtnVisible" :title="$t('settings.reset_avatar')" - @click="resetAvatar" class="button-unstyled reset-button" + @click="resetAvatar" > <FAIcon icon="times" diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index abf37062..5337d150 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -32,8 +32,8 @@ const Mfa = { components: { 'recovery-codes': RecoveryCodes, 'totp-item': TOTP, - 'qrcode': VueQrcode, - 'confirm': Confirm + qrcode: VueQrcode, + confirm: Confirm }, computed: { canSetupOTP () { @@ -139,7 +139,7 @@ const Mfa = { // fetch settings from server async fetchSettings () { - let result = await this.backendInteractor.settingsMFA() + const result = await this.backendInteractor.settingsMFA() if (result.error) return this.settings = result.settings this.settings.available = true diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index 8408d8e9..b0adb530 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -10,7 +10,7 @@ export default { inProgress: false // progress peform request to disable otp method }), components: { - 'confirm': Confirm + confirm: Confirm }, computed: { isActivated () { diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js index fc732936..d253bc79 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -13,7 +13,7 @@ const SecurityTab = { deletingAccount: false, deleteAccountConfirmPasswordInput: '', deleteAccountError: false, - changePasswordInputs: [ '', '', '' ], + changePasswordInputs: ['', '', ''], changedPassword: false, changePasswordError: false, moveAccountTarget: '', diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue index f266b603..ba6bd529 100644 --- a/src/components/settings_modal/tabs/theme_tab/preview.vue +++ b/src/components/settings_modal/tabs/theme_tab/preview.vue @@ -29,7 +29,10 @@ {{ $t('settings.style.preview.content') }} </h4> - <i18n-t scope="global" keypath="settings.style.preview.text"> + <i18n-t + scope="global" + keypath="settings.style.preview.text" + > <code style="font-family: var(--postCodeFont)"> {{ $t('settings.style.preview.mono') }} </code> diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js index 7e1da7ab..282cb384 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -95,11 +95,11 @@ export default { ...Object.keys(SLOT_INHERITANCE) .map(key => [key, '']) - .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}), + .reduce((acc, [key, val]) => ({ ...acc, [key + 'ColorLocal']: val }), {}), ...Object.keys(OPACITIES) .map(key => [key, '']) - .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}), + .reduce((acc, [key, val]) => ({ ...acc, [key + 'OpacityLocal']: val }), {}), shadowSelected: undefined, shadowsLocal: {}, @@ -212,12 +212,12 @@ export default { currentColors () { return Object.keys(SLOT_INHERITANCE) .map(key => [key, this[key + 'ColorLocal']]) - .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) + .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {}) }, currentOpacity () { return Object.keys(OPACITIES) .map(key => [key, this[key + 'OpacityLocal']]) - .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) + .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {}) }, currentRadii () { return { diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index 386a5756..a1d1012b 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -112,9 +112,11 @@ export default { return hex2rgb(this.selected.color) }, style () { - return this.ready ? { - boxShadow: getCssShadow(this.fallback) - } : {} + return this.ready + ? { + boxShadow: getCssShadow(this.fallback) + } + : {} } } } diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue index f2fc7b99..669cac71 100644 --- a/src/components/shadow_control/shadow_control.vue +++ b/src/components/shadow_control/shadow_control.vue @@ -215,7 +215,7 @@ </div> </template> -<script src="./shadow_control.js" ></script> +<script src="./shadow_control.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/shout_panel/shout_panel.js b/src/components/shout_panel/shout_panel.js index a6168971..fb0c5aa2 100644 --- a/src/components/shout_panel/shout_panel.js +++ b/src/components/shout_panel/shout_panel.js @@ -11,7 +11,7 @@ library.add( ) const shoutPanel = { - props: [ 'floating' ], + props: ['floating'], data () { return { currentMessage: '', diff --git a/src/components/shout_panel/shout_panel.vue b/src/components/shout_panel/shout_panel.vue index 1eca88a7..688c2d61 100644 --- a/src/components/shout_panel/shout_panel.vue +++ b/src/components/shout_panel/shout_panel.vue @@ -80,7 +80,7 @@ .floating-shout { position: fixed; bottom: 0.5em; - z-index: 1000; + z-index: var(--ZI_popovers); max-width: 25em; &.-left { diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index bad1806b..f45f8def 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -32,7 +32,7 @@ library.add( ) const SideDrawer = { - props: [ 'logout' ], + props: ['logout'], data: () => ({ closed: true, closeGesture: undefined diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index dd88de7d..7547fb08 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -204,14 +204,14 @@ </div> </template> -<script src="./side_drawer.js" ></script> +<script src="./side_drawer.js"></script> <style lang="scss"> @import '../../_variables.scss'; .side-drawer-container { position: fixed; - z-index: 1000; + z-index: var(--ZI_navbar); top: 0; left: 0; width: 100%; diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js index b9561bf1..a7fbc718 100644 --- a/src/components/staff_panel/staff_panel.js +++ b/src/components/staff_panel/staff_panel.js @@ -17,8 +17,8 @@ const StaffPanel = { const groupedStaffAccounts = groupBy(staffAccounts, 'role') return [ - { role: 'admin', users: groupedStaffAccounts['admin'] }, - { role: 'moderator', users: groupedStaffAccounts['moderator'] } + { role: 'admin', users: groupedStaffAccounts.admin }, + { role: 'moderator', users: groupedStaffAccounts.moderator } ].filter(group => group.users) }, ...mapGetters([ diff --git a/src/components/staff_panel/staff_panel.vue b/src/components/staff_panel/staff_panel.vue index c52ade42..6b9e61f2 100644 --- a/src/components/staff_panel/staff_panel.vue +++ b/src/components/staff_panel/staff_panel.vue @@ -24,7 +24,7 @@ </div> </template> -<script src="./staff_panel.js" ></script> +<script src="./staff_panel.js"></script> <style lang="scss"> diff --git a/src/components/status/status.js b/src/components/status/status.js index a925f30b..384063a7 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -4,13 +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 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' @@ -105,7 +105,6 @@ const Status = { RetweetButton, ExtraButtons, PostStatusForm, - UserCard, UserAvatar, AvatarList, Timeago, @@ -115,7 +114,8 @@ const Status = { StatusContent, RichContent, MentionLink, - MentionsLine + MentionsLine, + UserPopover }, props: [ 'statusoid', @@ -361,6 +361,7 @@ const Status = { return uniqBy(combinedUsers, 'id') }, tags () { + // eslint-disable-next-line no-prototype-builtins return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ') }, hidePostStats () { @@ -448,7 +449,7 @@ const Status = { scrollIfHighlighted (highlightId) { const id = highlightId if (this.status.id === id) { - let rect = this.$el.getBoundingClientRect() + const rect = this.$el.getBoundingClientRect() if (rect.top < 100) { // Post is above screen, match its top to screen top window.scrollBy(0, rect.top - 100) @@ -463,7 +464,7 @@ const Status = { } }, watch: { - 'highlight': function (id) { + highlight: function (id) { this.scrollIfHighlighted(id) }, 'status.repeat_num': function (num) { @@ -478,7 +479,7 @@ const Status = { this.$store.dispatch('fetchFavs', this.status.id) } }, - 'isSuspendable': function (val) { + isSuspendable: function (val) { this.suspendable = val } } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 67ce999a..967a966c 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -124,25 +124,23 @@ > <a :href="$router.resolve(userProfileLink).href" - @click.stop.prevent.capture="toggleUserExpanded" + @click.prevent > - <UserAvatar - class="post-avatar" - :bot="botIndicator" - :compact="compact" - :better-shadow="betterShadow" - :user="status.user" - /> + <UserPopover + :user-id="status.user.id" + :overlay-centers="true" + > + <UserAvatar + class="post-avatar" + :bot="botIndicator" + :compact="compact" + :better-shadow="betterShadow" + :user="status.user" + /> + </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" @@ -322,6 +320,7 @@ class="mentions-line-first" /> </span> + {{ ' ' }} <MentionsLine v-if="hasMentionsLine" :mentions="mentionsLine.slice(1)" @@ -492,6 +491,6 @@ </div> </template> -<script src="./status.js" ></script> +<script src="./status.js"></script> <style src="./status.scss" lang="scss"></style> diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue index 976fe98c..fb356360 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -96,5 +96,5 @@ <slot v-if="!hideSubjectStatus" /> </div> </template> -<script src="./status_body.js" ></script> +<script src="./status_body.js"></script> <style lang="scss" src="./status_body.scss" /> diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue index 9e7d7956..e2120f7a 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -56,7 +56,7 @@ </div> </template> -<script src="./status_content.js" ></script> +<script src="./status_content.js"></script> <style lang="scss"> .StatusContent { flex: 1; diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js index e0962ccd..c55bd85b 100644 --- a/src/components/status_popover/status_popover.js +++ b/src/components/status_popover/status_popover.js @@ -38,6 +38,13 @@ const StatusPopover = { .catch(e => (this.error = true)) } } + }, + watch: { + status (newStatus, oldStatus) { + if (newStatus !== oldStatus) { + this.$nextTick(() => this.$refs.popover.updateStyles()) + } + } } } diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue index fdca8c9c..f4ab357b 100644 --- a/src/components/status_popover/status_popover.vue +++ b/src/components/status_popover/status_popover.vue @@ -1,14 +1,16 @@ <template> <Popover + ref="popover" trigger="hover" + :stay-on-click="true" popover-class="popover-default status-popover" :bound-to="{ x: 'container' }" @show="enter" > - <template v-slot:trigger> + <template #trigger> <slot /> </template> - <template v-slot:content> + <template #content> <Status v-if="status" :is-preview="true" @@ -35,7 +37,7 @@ </Popover> </template> -<script src="./status_popover.js" ></script> +<script src="./status_popover.js"></script> <style lang="scss"> @import '../../_variables.scss'; @@ -52,8 +54,6 @@ 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); /* TODO cleanup this */ .Status.Status { diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js index 3a2d3914..b06384e5 100644 --- a/src/components/sticker_picker/sticker_picker.js +++ b/src/components/sticker_picker/sticker_picker.js @@ -31,8 +31,8 @@ const StickerPicker = { fetch(sticker) .then((res) => { res.blob().then((blob) => { - var file = new File([blob], name, { mimetype: 'image/png' }) - var formData = new FormData() + const file = new File([blob], name, { mimetype: 'image/png' }) + const formData = new FormData() formData.append('file', file) statusPosterService.uploadMedia({ store, formData }) .then((fileData) => { diff --git a/src/components/terms_of_service_panel/terms_of_service_panel.vue b/src/components/terms_of_service_panel/terms_of_service_panel.vue index 63dc58b8..1df41d70 100644 --- a/src/components/terms_of_service_panel/terms_of_service_panel.vue +++ b/src/components/terms_of_service_panel/terms_of_service_panel.vue @@ -13,7 +13,7 @@ </div> </template> -<script src="./terms_of_service_panel.js" ></script> +<script src="./terms_of_service_panel.js"></script> <style lang="scss"> .tos-content { diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index f65881b6..266c1d9a 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -46,7 +46,10 @@ </div> </div> <div :class="classes.footer"> - <teleport :to="footerSlipgate" :disabled="!embedded || !footerSlipgate"> + <teleport + :to="footerSlipgate" + :disabled="!embedded || !footerSlipgate" + > <div v-if="count===0" class="new-status-notification text-center faint" diff --git a/src/components/timeline/timeline_quick_settings.vue b/src/components/timeline/timeline_quick_settings.vue index 98fab926..297bc72a 100644 --- a/src/components/timeline/timeline_quick_settings.vue +++ b/src/components/timeline/timeline_quick_settings.vue @@ -4,7 +4,7 @@ class="TimelineQuickSettings" :bound-to="{ x: 'container' }" > - <template v-slot:content> + <template #content> <div class="dropdown-menu"> <div v-if="loggedIn"> <button @@ -80,7 +80,7 @@ </button> </div> </template> - <template v-slot:trigger> + <template #trigger> <button class="button-unstyled"> <FAIcon icon="filter" /> </button> diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index bab51e75..a11e7b7e 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -11,9 +11,9 @@ library.add(faChevronDown) // because nav panel benefits from the same information. export const timelineNames = () => { return { - 'friends': 'nav.home_timeline', - 'bookmarks': 'nav.bookmarks', - 'dms': 'nav.dms', + friends: 'nav.home_timeline', + bookmarks: 'nav.bookmarks', + dms: 'nav.dms', 'public-timeline': 'nav.public_tl', 'public-external-timeline': 'nav.twkn' } diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue index 61119482..c24b9d72 100644 --- a/src/components/timeline_menu/timeline_menu.vue +++ b/src/components/timeline_menu/timeline_menu.vue @@ -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 #content> + <TimelineMenuContent /> </template> - <template v-slot:trigger> - <button class="button-unstyled title timeline-menu-title"> + <template #trigger> + <span class="button-unstyled title timeline-menu-title"> <span class="timeline-title">{{ timelineName() }}</span> <span> <FAIcon @@ -27,53 +25,29 @@ class="click-blocker" @click="blockOpen" /> - </button> + </span> </template> </Popover> </template> -<script src="./timeline_menu.js" ></script> +<script src="./timeline_menu.js"></script> <style lang="scss"> @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; @@ -134,7 +118,9 @@ a { display: block; - padding: 0.6em 0.65em; + padding: 0 0.65em; + height: 3.5em; + line-height: 3.5em; &:hover { background-color: $fallback--lightBg; diff --git a/src/components/timeline_menu/timeline_menu_content.vue b/src/components/timeline_menu/timeline_menu_content.vue index bed1b679..59e9e43c 100644 --- a/src/components/timeline_menu/timeline_menu_content.vue +++ b/src/components/timeline_menu/timeline_menu_content.vue @@ -63,4 +63,4 @@ </ul> </template> -<script src="./timeline_menu_content.js" ></script> +<script src="./timeline_menu_content.js"></script> diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 4168c54a..4c81e2c7 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -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,15 +58,16 @@ 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 () { return { backgroundImage: [ - `linear-gradient(to bottom, var(--profileTint), var(--profileTint))`, + 'linear-gradient(to bottom, var(--profileTint), var(--profileTint))', `url(${this.user.cover_photo})` ].join(', ') } @@ -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() + } } } } diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss index 2e153120..a0bbc6a6 100644 --- a/src/components/user_card/user_card.scss +++ b/src/components/user_card/user_card.scss @@ -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); } - &-bordered { + &.-popover { + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + + --__roundnessTop: var(--tooltipRadius); + --__roundnessBottom: var(--tooltipRadius); + } + + &.-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 { diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 67837845..ace89c51 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -8,25 +8,32 @@ :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" - class="user-info-avatar-link" + v-if="avatarAction === 'zoom'" + class="user-info-avatar -link" @click="zoomAvatar" > <UserAvatar :better-shadow="betterShadow" :user="user" /> - <div class="user-info-avatar-link-overlay"> + <div class="user-info-avatar -link -overlay"> <FAIcon class="fa-scale-110 fa-old-padding" icon="search-plus" /> </div> </a> + <UserAvatar + v-else-if="typeof avatarAction === 'function'" + class="user-info-avatar" + :better-shadow="betterShadow" + :user="user" + @click="avatarAction" + /> <router-link v-else :to="userProfileLink(user)" @@ -38,12 +45,16 @@ </router-link> <div class="user-summary"> <div class="top-line"> - <RichContent - :title="user.name" + <router-link + :to="userProfileLink(user)" class="user-name" - :html="user.name" - :emoji="user.emoji" - /> + > + <RichContent + :title="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 diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue index bdc3aa92..a3ce54c3 100644 --- a/src/components/user_list_popover/user_list_popover.vue +++ b/src/components/user_list_popover/user_list_popover.vue @@ -4,10 +4,10 @@ placement="top" :offset="{ y: 5 }" > - <template v-slot:trigger> + <template #trigger> <slot /> </template> - <template v-slot:content> + <template #content> <div class="user-list-popover"> <template v-if="users.length"> <div @@ -45,7 +45,7 @@ </Popover> </template> -<script src="./user_list_popover.js" ></script> +<script src="./user_list_popover.js"></script> <style lang="scss"> @import '../../_variables.scss'; diff --git a/src/components/user_popover/user_popover.js b/src/components/user_popover/user_popover.js new file mode 100644 index 00000000..69b25383 --- /dev/null +++ b/src/components/user_popover/user_popover.js @@ -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 diff --git a/src/components/user_popover/user_popover.vue b/src/components/user_popover/user_popover.vue new file mode 100644 index 00000000..4e999672 --- /dev/null +++ b/src/components/user_popover/user_popover.vue @@ -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 #trigger> + <slot /> + </template> + <template #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> diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 62792599..d0da2b5b 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -8,7 +8,7 @@ :user-id="userId" :switcher="true" :selected="timeline.viewing" - :allow-zooming-avatar="true" + avatar-action="zoom" rounded="top" /> <div @@ -56,7 +56,7 @@ :user-id="userId" :pinned-status-ids="user.pinnedStatusIds" :in-profile="true" - :footerSlipgate="footerRef" + :footer-slipgate="footerRef" /> <div v-if="followsTabVisible" @@ -65,7 +65,7 @@ :disabled="!user.friends_count" > <FriendList :user-id="userId"> - <template v-slot:item="{item}"> + <template #item="{item}"> <FollowCard :user="item" /> </template> </FriendList> @@ -77,7 +77,7 @@ :disabled="!user.followers_count" > <FollowerList :user-id="userId"> - <template v-slot:item="{item}"> + <template #item="{item}"> <FollowCard :user="item" :no-follows-you="isUs" @@ -95,7 +95,7 @@ :timeline="media" :user-id="userId" :in-profile="true" - :footerSlipgate="footerRef" + :footer-slipgate="footerRef" /> <Timeline v-if="isUs" @@ -107,10 +107,13 @@ timeline-name="favorites" :timeline="favorites" :in-profile="true" - :footerSlipgate="footerRef" + :footer-slipgate="footerRef" /> </tab-switcher> - <div class="panel-footer" :ref="setFooterRef"></div> + <div + :ref="setFooterRef" + class="panel-footer" + /> </div> <div v-else diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue index 030ce2c4..429a66e2 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.vue +++ b/src/components/user_reporting_modal/user_reporting_modal.vue @@ -45,7 +45,7 @@ </div> <div class="user-reporting-panel-right"> <List :items="statuses"> - <template v-slot:item="{item}"> + <template #item="{item}"> <div class="status-fadein user-reporting-panel-sitem"> <Status :in-conversation="false" diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index ecd97dd7..53f05272 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -28,7 +28,7 @@ const WhoToFollow = { getWhoToFollow () { const credentials = this.$store.state.users.currentUser.credentials if (credentials) { - apiService.suggestions({ credentials: credentials }) + apiService.suggestions({ credentials }) .then((reply) => { this.showWhoToFollow(reply) }) diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index 818e8bd5..f19ba948 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -6,9 +6,9 @@ function showWhoToFollow (panel, reply) { const shuffled = shuffle(reply) panel.usersToFollow.forEach((toFollow, index) => { - let user = shuffled[index] - let img = user.avatar || this.$store.state.instance.defaultAvatar - let name = user.acct + const user = shuffled[index] + const img = user.avatar || this.$store.state.instance.defaultAvatar + const name = user.acct toFollow.img = img toFollow.name = name @@ -24,12 +24,12 @@ function showWhoToFollow (panel, reply) { } function getWhoToFollow (panel) { - var credentials = panel.$store.state.users.currentUser.credentials + const credentials = panel.$store.state.users.currentUser.credentials if (credentials) { panel.usersToFollow.forEach(toFollow => { toFollow.name = 'Loading...' }) - apiService.suggestions({ credentials: credentials }) + apiService.suggestions({ credentials }) .then((reply) => { showWhoToFollow(panel, reply) }) diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.vue b/src/components/who_to_follow_panel/who_to_follow_panel.vue index 518acd97..c1ba6fb1 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.vue +++ b/src/components/who_to_follow_panel/who_to_follow_panel.vue @@ -27,7 +27,7 @@ </div> </template> -<script src="./who_to_follow_panel.js" ></script> +<script src="./who_to_follow_panel.js"></script> <style lang="scss"> .who-to-follow * { |
