diff options
Diffstat (limited to 'src')
146 files changed, 1283 insertions, 710 deletions
@@ -4,7 +4,6 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance import FeaturesPanel from './components/features_panel/features_panel.vue' import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import ShoutPanel from './components/shout_panel/shout_panel.vue' -import SettingsModal from './components/settings_modal/settings_modal.vue' import MediaModal from './components/media_modal/media_modal.vue' import SideDrawer from './components/side_drawer/side_drawer.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' @@ -32,7 +31,7 @@ export default { MobilePostStatusButton, MobileNav, DesktopNav, - SettingsModal, + SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')), UserReportingModal, PostStatusModal, GlobalNoticeList diff --git a/src/App.scss b/src/App.scss index 7e6d0dfc..ab025d63 100644 --- a/src/App.scss +++ b/src/App.scss @@ -4,6 +4,13 @@ :root { --navbar-height: 3.5rem; --post-line-height: 1.4; + // Z-Index stuff + --ZI_media_modal: 90000; + --ZI_modals_popovers: 85000; + --ZI_modals: 80000; + --ZI_navbar_popovers: 75000; + --ZI_navbar: 70000; + --ZI_popovers: 60000; } html { @@ -117,7 +124,7 @@ i[class*=icon-], } nav { - z-index: 1000; + z-index: var(--ZI_navbar); color: var(--topBarText); background-color: $fallback--fg; background-color: var(--topBar, $fallback--fg); @@ -828,7 +835,7 @@ option { // Vue transitions .fade-enter-active, .fade-leave-active { - transition: opacity 0.2s; + transition: opacity 0.3s; } .fade-enter-from, diff --git a/src/App.vue b/src/App.vue index 21f6f686..0efadaf0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,8 +15,12 @@ class="app-layout container" :class="classes" > - <div class="underlay"/> - <div id="sidebar" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"> + <div class="underlay" /> + <div + id="sidebar" + class="column -scrollable" + :class="{ '-show-scrollbar': showScrollbars }" + > <user-panel /> <template v-if="layoutType !== 'mobile'"> <nav-panel /> @@ -26,7 +30,11 @@ <div id="notifs-sidebar" /> </template> </div> - <div id="main-scroller" class="column main" :class="{ '-full-height': isChats }"> + <div + id="main-scroller" + class="column main" + :class="{ '-full-height': isChats }" + > <div v-if="!currentUser" class="login-hint panel panel-default" @@ -40,9 +48,13 @@ </div> <router-view /> </div> - <div id="notifs-column" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"/> + <div + id="notifs-column" + class="column -scrollable" + :class="{ '-show-scrollbar': showScrollbars }" + /> </div> - <media-modal /> + <MediaModal /> <shout-panel v-if="currentUser && shout && !hideShoutbox" :floating="true" @@ -55,6 +67,7 @@ <SettingsModal /> <div id="modal" /> <GlobalNoticeList /> + <div id="popovers" /> </div> </template> diff --git a/src/boot/after_store.js b/src/boot/after_store.js index f655c38f..908d905a 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -156,7 +156,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('hideSitename') copyInstanceOption('sidebarRight') - return store.dispatch('setTheme', config['theme']) + return store.dispatch('setTheme', config.theme) } const getTOS = async ({ store }) => { @@ -197,7 +197,7 @@ const getStickers = async ({ store }) => { const stickers = (await Promise.all( Object.entries(values).map(async ([name, path]) => { const resPack = await window.fetch(path + 'pack.json') - var meta = {} + let meta = {} if (resPack.ok) { meta = await resPack.json() } @@ -319,6 +319,7 @@ const setConfig = async ({ store }) => { } const checkOAuthToken = async ({ store }) => { + // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { if (store.getters.getUserToken()) { try { @@ -396,6 +397,9 @@ const afterStoreSetup = async ({ store, i18n }) => { app.component('FAIcon', FontAwesomeIcon) app.component('FALayers', FontAwesomeLayers) + // remove after vue 3.3 + app.config.unwrapInjectedRef = true + app.mount('#app') return app diff --git a/src/boot/routes.js b/src/boot/routes.js index 726476a8..c8194d5f 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -31,7 +31,8 @@ export default (store) => { } let routes = [ - { name: 'root', + { + name: 'root', path: '/', redirect: _to => { return (store.state.users.currentUser @@ -45,12 +46,14 @@ export default (store) => { { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, - { name: 'remote-user-profile-acct', + { + name: 'remote-user-profile-acct', path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)', component: RemoteUserResolver, beforeEnter: validateAuthenticatedRoute }, - { name: 'remote-user-profile', + { + name: 'remote-user-profile', path: '/remote-users/:hostname/:username', component: RemoteUserResolver, beforeEnter: validateAuthenticatedRoute 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 * { diff --git a/src/i18n/en.json b/src/i18n/en.json index 51e1a0c3..c8729bcd 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -238,8 +238,9 @@ } }, "registration": { - "bio": "Bio", + "bio_optional": "Bio (optional)", "email": "Email", + "email_optional": "Email (optional)", "fullname": "Display name", "password_confirm": "Password confirmation", "registration": "Registration", @@ -560,10 +561,12 @@ "mention_link_display_short": "always as short names (e.g. {'@'}foo)", "mention_link_display_full_for_remote": "as full names only for remote users (e.g. {'@'}foo{'@'}example.org)", "mention_link_display_full": "always as full names (e.g. {'@'}foo{'@'}example.org)", - "mention_link_show_tooltip": "Show full user names as tooltip for remote users", + "mention_link_use_tooltip": "Show user card when clicking mention links", "mention_link_show_avatar": "Show user avatar beside the link", "mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)", "mention_link_bolden_you": "Highlight mention of you when you are mentioned", + "user_popover_avatar_zoom": "Clicking on user avatar in popover zooms it instead of closing the popover", + "user_popover_avatar_overlay": "Show user popover over user avatar", "fun": "Fun", "greentext": "Meme arrows", "show_yous": "Show (You)s", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index fae7c7a2..306ed184 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -15,7 +15,8 @@ "title": "Fonctionnalités", "who_to_follow": "Suggestions de suivis", "pleroma_chat_messages": "Chat Pleroma", - "upload_limit": "Limite de téléversement" + "upload_limit": "Limite de téléversement", + "shout": "Shoutbox" }, "finder": { "error_fetching_user": "Erreur lors de la recherche du compte", @@ -44,9 +45,15 @@ "moderator": "Modo'", "admin": "Admin" }, - "flash_content": "Clique pour afficher le contenu Flash avec Ruffle (Expérimental, peut ne pas fonctionner).", + "flash_content": "Cliquer pour afficher le contenu Flash avec Ruffle (Expérimental, peut ne pas fonctionner).", "flash_security": "Cela reste potentiellement dangereux, Flash restant du code arbitraire.", - "flash_fail": "Échec de chargement du contenu Flash, voir la console pour les détails." + "flash_fail": "Échec de chargement du contenu Flash, voir la console pour les détails.", + "scope_in_timeline": { + "direct": "Direct", + "public": "Publique", + "private": "Abonné⋅e⋅s seulement", + "unlisted": "Non-listé" + } }, "image_cropper": { "crop_picture": "Rogner l'image", @@ -79,7 +86,9 @@ }, "media_modal": { "previous": "Précédent", - "next": "Suivant" + "next": "Suivant", + "counter": "{current} / {total}", + "hide": "Fermer le visualiseur multimédia" }, "nav": { "about": "À propos", @@ -114,7 +123,8 @@ "migrated_to": "a migré à", "reacted_with": "a réagi avec {0}", "follow_request": "veut vous suivre", - "error": "Erreur de chargement des notifications : {0}" + "error": "Erreur de chargement des notifications : {0}", + "poll_ended": "Sondage terminé" }, "interactions": { "favs_repeats": "Partages et favoris", @@ -178,7 +188,8 @@ }, "reason_placeholder": "Cette instance modère les inscriptions manuellement.\nExpliquer ce qui motive votre inscription à l'administration.", "reason": "Motivation d'inscription", - "register": "Enregistrer" + "register": "Enregistrer", + "email_language": "Dans quelle langue voulez-vous recevoir les emails du server ?" }, "selectable_list": { "select_all": "Tout selectionner" @@ -267,8 +278,8 @@ "import_theme": "Charger le thème", "inputRadius": "Champs de texte", "checkboxRadius": "Cases à cocher", - "instance_default": "(default : {value})", - "instance_default_simple": "(default)", + "instance_default": "(défaut : {value})", + "instance_default_simple": "(défaut)", "interface": "Interface", "interfaceLanguage": "Langue de l'interface", "invalid_theme_imported": "Le fichier sélectionné n'est pas un thème Pleroma pris en charge. Aucun changement n'a été apporté à votre thème.", @@ -570,7 +581,71 @@ "restore_settings": "Restaurer les paramètres depuis un fichier" }, "hide_shoutbox": "Cacher la shoutbox de l'instance", - "right_sidebar": "Afficher le paneau latéral à droite" + "right_sidebar": "Afficher le paneau latéral à droite", + "expert_mode": "Préférences Avancées", + "post_look_feel": "Affichage des messages", + "mention_links": "Liens des mentions", + "email_language": "Langue pour recevoir les emails du server", + "account_backup_table_head": "Sauvegarde", + "download_backup": "Télécharger", + "backup_not_ready": "La sauvegarde n'est pas encore prête.", + "remove_backup": "Supprimer", + "list_backups_error": "Erreur d'obtention de la liste des sauvegardes : {error}", + "add_backup": "Créer une nouvelle sauvegarde", + "added_backup": "Ajouter une nouvelle sauvegarde.", + "account_alias": "Alias du compte", + "account_alias_table_head": "Alias", + "list_aliases_error": "Erreur à l'obtention des alias : {error}", + "hide_list_aliases_error_action": "Fermer", + "remove_alias": "Supprimer cet alias", + "new_alias_target": "Ajouter un nouvel alias (ex. {example})", + "added_alias": "L'alias à été ajouté.", + "add_alias_error": "Erreur à l'ajout de l'alias : {error}", + "move_account_target": "Compte cible (ex. {example})", + "moved_account": "Compte déplacé.", + "move_account_error": "Erreur au déplacement du compte : {error}", + "wordfilter": "Filtrage de mots", + "mute_bot_posts": "Masquer les messages des robots", + "hide_bot_indication": "Cacher l'indication d'un robot avec les messages", + "always_show_post_button": "Toujours montrer le bouton flottant Nouveau Message", + "hide_muted_threads": "Cacher les fils masqués", + "account_privacy": "Intimité", + "posts": "Messages", + "disable_sticky_headers": "Ne pas coller les en-têtes des colonnes en haut de l'écran", + "show_scrollbars": "Montrer les ascenseurs des colonnes", + "third_column_mode_none": "Jamais afficher la troisième colonne", + "third_column_mode_notifications": "Colonne de notifications", + "third_column_mode_postform": "Édition de messages et navigation", + "tree_advanced": "Permettre une navigation plus flexible dans l'arborescence", + "conversation_display_linear": "Style linéaire", + "conversation_other_replies_button": "Montrer le bouton \"autres réponses\"", + "conversation_other_replies_button_below": "En-dessous des messages", + "conversation_other_replies_button_inside": "Dans les messages", + "max_depth_in_thread": "Profondeur maximum à afficher par défaut dans un fil", + "mention_link_display": "Afficher les mentions", + "mention_link_display_full_for_remote": "complet pour les comptes distants (ex. {'@'}foo{'@'}example.org)", + "mention_link_display_full": "toujours complet (ex. {'@'}foo{'@'}example.org)", + "mention_link_show_avatar": "Afficher les avatars à coté du lien", + "mention_link_fade_domain": "Estomper les domaines (ex. {'@'}example.org en {'@'}foo{'@'}example.org)", + "mention_link_bolden_you": "Surligner les mentions qui vous sont destinées", + "show_yous": "Afficher (Vous)", + "setting_server_side": "Cette préférence est liée au profile et affecte toutes les sessions et clients", + "account_backup": "Sauvegarde de compte", + "account_backup_description": "Ceci permet de télécharger une archive des informations du compte et vos messages, mais ils ne peuvent pas actuellement être importé dans un compte Pleroma.", + "add_backup_error": "Erreur à l'ajout d'une nouvelle sauvegarde : {error}", + "move_account": "Déplacer le compte", + "move_account_notes": "Si vous voulez déplacer le compte ailleurs, vous devez aller sur votre compte cible et y créer un alias pointant ici.", + "hide_wordfiltered_statuses": "Cacher les messages filtré par un mot", + "user_profiles": "Profils des utilisateur⋅ice⋅s", + "notification_visibility_polls": "Fins de sondage auquel vous avez voté·e", + "hide_favorites_description": "Ne pas montrer ma liste de favoris (les personnes sont quand même notifiés)", + "conversation_display": "Style d'affichage des conversations", + "conversation_display_tree": "Arborescence", + "third_column_mode": "Quand il-y-a assez d'espace, afficher une troisième colonne avec", + "tree_fade_ancestors": "Montrer les parents du message courant en texte léger", + "use_at_icon": "Montrer le symbole {'@'} comme une icône au lieu de textuelle", + "mention_link_display_short": "toujours raccourcies (ex. {'@'}foo)", + "mention_link_show_tooltip": "Montrer le nom complet pour les comptes distants dans une info-bulle" }, "timeline": { "collapse": "Fermer", @@ -613,7 +688,33 @@ "thread_muted": "Fil de discussion masqué", "external_source": "Source externe", "unbookmark": "Supprimer des favoris", - "bookmark": "Ajouter aux favoris" + "bookmark": "Ajouter aux favoris", + "plus_more": "plus +{number}", + "many_attachments": "Message avec {number} pièce(s)-jointe(s)", + "collapse_attachments": "Réduire les pièces jointes", + "show_attachment_in_modal": "Montrer dans le visionneur de médias", + "hide_attachment": "Cacher la pièce jointe", + "you": "(Vous)", + "attachment_stop_flash": "Arrêter Flash Player", + "move_down": "Décaler la pièce-jointe à droite", + "thread_hide": "Cacher ce fil", + "thread_show": "Montrer ce fil", + "thread_show_full_with_icon": "{icon} {text}", + "thread_follow": "Montrer le reste du fil ({numStatus} message, {depth} niveaux maximum) | Montrer le reste du fil ({numStatus} messages, {depth} niveaux maximum)", + "thread_follow_with_icon": "{icon} {text}", + "ancestor_follow": "Monter les {numReplies} autres réponses après ce message | Monter les {numReplies} autres réponses après ce message", + "ancestor_follow_with_icon": "{icon} {text}", + "show_all_conversation_with_icon": "{icon} {text}", + "show_only_conversation_under_this": "Montrer uniquement les réponses à ce message", + "mentions": "Mentions", + "replies_list_with_others": "Réponses (+{numReplies} autres) : | Réponses (+{numReplies} autres) :", + "show_all_attachments": "Montrer toutes les pièces jointes", + "show_attachment_description": "Prévisualiser la description (ouvrir la pièce-jointe pour la description complète)", + "remove_attachment": "Enlever la pièce jointe", + "move_up": "Décaler la pièce-jointe à gauche", + "open_gallery": "Ouvrir la galerie", + "thread_show_full": "Montrer tout le fil ({numStatus} message, {depth} niveaux maximum) | Montrer tout le fil ({numStatus} messages, {depth} niveaux maximum)", + "show_all_conversation": "Montrer tout le fil ({numStatus} autre message) | Montrer tout le fil ({numStatus} autre messages)" }, "user_card": { "approve": "Accepter", @@ -644,11 +745,11 @@ "unmute_progress": "Démasquage…", "mute_progress": "Masquage…", "admin_menu": { - "moderation": "Moderation", + "moderation": "Modération", "grant_admin": "Promouvoir Administrateur⋅ice", - "revoke_admin": "Dégrader Administrateur⋅ice", + "revoke_admin": "Dégrader L'administrateur⋅ice", "grant_moderator": "Promouvoir Modérateur⋅ice", - "revoke_moderator": "Dégrader Modérateur⋅ice", + "revoke_moderator": "Dégrader la·e modérateur⋅ice", "activate_account": "Activer le compte", "deactivate_account": "Désactiver le compte", "delete_account": "Supprimer le compte", @@ -659,7 +760,8 @@ "disable_remote_subscription": "Interdir de s'abonner a l'utilisateur depuis l'instance distante", "disable_any_subscription": "Interdir de s'abonner à l'utilisateur tout court", "quarantine": "Interdir les statuts de l'utilisateur à fédérer", - "delete_user": "Supprimer l'utilisateur" + "delete_user": "Supprimer l'utilisateur", + "delete_user_data_and_deactivate_confirmation": "Ceci va supprimer les données du compte de manière permanente et le désactivé. Êtes-vous vraiment sûr ?" }, "mention": "Mention", "hidden": "Caché", @@ -679,7 +781,9 @@ "striped": "Fond rayé" }, "bot": "Robot", - "edit_profile": "Éditer le profil" + "edit_profile": "Éditer le profil", + "deactivated": "Désactivé", + "follow_cancel": "Annuler la requête" }, "user_profile": { "timeline_title": "Flux du compte", @@ -747,13 +851,16 @@ "media_removal_desc": "Cette instance supprime le contenu multimédia des instances suivantes :", "media_nsfw": "Force le contenu multimédia comme sensible", "ftl_removal": "Supprimées du flux fédéré", - "media_nsfw_desc": "Cette instance force les pièce-jointes comme sensible pour les messages des instances suivantes :" + "media_nsfw_desc": "Cette instance force les pièce-jointes comme sensible pour les messages des instances suivantes :", + "reason": "Raison", + "not_applicable": "N/A", + "instance": "Instance" }, "federation": "Fédération", "mrf_policies": "Politiques MRF actives", "mrf_policies_desc": "Les politiques MRF modifient la fédération entre les instances. Les politiques suivantes sont activées :" }, - "staff": "Staff" + "staff": "Équipe" }, "domain_mute_card": { "mute": "Masqué", @@ -825,7 +932,23 @@ "year": "{0} année", "years": "{0} années", "year_short": "{0}a", - "years_short": "{0}a" + "years_short": "{0}a", + "unit": { + "years": "{0} année | {0} années", + "years_short": "{0}ans", + "days_short": "{0}j", + "hours": "{0} heure | {0} heures", + "hours_short": "{0}h", + "minutes": "{0} minute | {0} minutes", + "minutes_short": "{0}min", + "months_short": "{0}mois", + "seconds": "{0} seconde | {0} secondes", + "seconds_short": "{0}s", + "weeks": "{0} semaine | {0} semaines", + "days": "{0} jour | {0} jours", + "months": "{0} mois | {0} mois", + "weeks_short": "{0}semaine" + } }, "search": { "people": "Comptes", diff --git a/src/i18n/messages.js b/src/i18n/messages.js index 18ed79b7..eae75c80 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -46,7 +46,7 @@ const messages = { }, setLanguage: async (i18n, language) => { if (loaders[language]) { - let messages = await loaders[language]() + const messages = await loaders[language]() i18n.setLocaleMessage(language, messages.default) } i18n.locale = language diff --git a/src/i18n/zh.json b/src/i18n/zh.json index dd0e6827..cf5f384c 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -15,7 +15,8 @@ "title": "功能", "who_to_follow": "推荐关注", "pleroma_chat_messages": "Pleroma 聊天", - "upload_limit": "上传限制" + "upload_limit": "上传限制", + "shout": "留言板" }, "finder": { "error_fetching_user": "获取用户时发生错误", @@ -46,7 +47,13 @@ }, "flash_content": "点击以使用 Ruffle 显示 Flash 内容(实验性,可能无效)。", "flash_security": "注意这可能有潜在的危险,因为 Flash 内容仍然是任意的代码。", - "flash_fail": "Flash 内容加载失败,请在控制台查看详情。" + "flash_fail": "Flash 内容加载失败,请在控制台查看详情。", + "scope_in_timeline": { + "public": "公开", + "direct": "私讯", + "private": "仅关注者", + "unlisted": "列外" + } }, "image_cropper": { "crop_picture": "裁剪图片", @@ -79,7 +86,9 @@ }, "media_modal": { "previous": "往前", - "next": "往后" + "next": "往后", + "hide": "关闭媒体查看器", + "counter": "{current} / {total}" }, "nav": { "about": "关于", @@ -114,7 +123,8 @@ "reacted_with": "作出了 {0} 的反应", "migrated_to": "迁移到了", "follow_request": "想要关注你", - "error": "取得通知时发生错误:{0}" + "error": "取得通知时发生错误:{0}", + "poll_ended": "投票结束了" }, "polls": { "add_poll": "增加投票", @@ -197,7 +207,8 @@ }, "reason_placeholder": "此实例的注册需要手动批准。\n请让管理员知道您为什么想要注册。", "reason": "注册理由", - "register": "注册" + "register": "注册", + "email_language": "你想从服务器收到什么语言的邮件?" }, "selectable_list": { "select_all": "选择全部" @@ -589,7 +600,38 @@ "backup_restore": "设置备份" }, "right_sidebar": "在右侧显示侧边栏", - "hide_shoutbox": "隐藏实例留言板" + "hide_shoutbox": "隐藏实例留言板", + "expert_mode": "显示高级", + "download_backup": "下载", + "mention_links": "提及链接", + "account_backup": "账号备份", + "account_backup_table_head": "备份", + "remove_backup": "移除", + "list_backups_error": "获取备份列表出错:{error}", + "add_backup": "创建一个新备份", + "added_backup": "创建了一个新备份。", + "account_alias": "账号别名", + "account_alias_table_head": "别名", + "list_aliases_error": "获取别名时出错:{error}", + "hide_list_aliases_error_action": "关闭", + "remove_alias": "移除这个别名", + "new_alias_target": "添加一个新别名(例如 {example})", + "added_alias": "别名添加好了。", + "move_account": "移动账号", + "move_account_target": "目标账号(例如 {example})", + "moved_account": "账号移动好了。", + "move_account_error": "移动账号时出错:{error}", + "setting_server_side": "这个设置是捆绑到你的个人资料的,能影响所有会话和客户端", + "post_look_feel": "文章的样子跟感受", + "email_language": "从服务器收邮件的语言", + "account_backup_description": "这个允许你下载一份账号信息和文章的存档,但是现在还不能导入到 Pleroma 账号里。", + "backup_not_ready": "备份还没准备好。", + "add_backup_error": "添加新备份时出错:{error}", + "add_alias_error": "添加别名时出错:{error}", + "move_account_notes": "如果你想把账号移动到别的地方,你必须去目标账号,然后加一个指向这里的别名。", + "wordfilter": "词语过滤器", + "user_profiles": "用户资料", + "third_column_mode_notifications": "消息栏" }, "time": { "day": "{0} 天", @@ -623,7 +665,23 @@ "year": "{0} 年", "years": "{0} 年", "year_short": "{0}y", - "years_short": "{0}y" + "years_short": "{0}y", + "unit": { + "days_short": "{0} 天", + "hours": "{0} 小时", + "hours_short": "{0} 时", + "minutes": "{0} 分", + "minutes_short": "{0} 分", + "months": "{0} 个月", + "months_short": "{0} 月", + "seconds": "{0} 秒", + "seconds_short": "{0} 秒", + "weeks_short": "{0} 周", + "years": "{0} 年", + "years_short": "{0} 年", + "weeks": "{0} 周", + "days": "{0} 天" + } }, "timeline": { "collapse": "折叠", @@ -666,7 +724,32 @@ "status_deleted": "该状态已被删除", "nsfw": "NSFW", "external_source": "外部来源", - "expand": "展开" + "expand": "展开", + "you": "(你)", + "plus_more": "还有 {number} 个", + "many_attachments": "文章有 {number} 个附件", + "collapse_attachments": "折起附件", + "show_all_attachments": "显示所有附件", + "show_attachment_description": "预览描述(打开附件能看完整描述)", + "hide_attachment": "隐藏附件", + "remove_attachment": "移除附件", + "attachment_stop_flash": "停止 Flash 播放器", + "move_up": "把附件左移", + "open_gallery": "打开图库", + "thread_hide": "隐藏这个线索", + "thread_show": "显示这个线索", + "thread_show_full_with_icon": "{icon} {text}", + "thread_follow": "查看这个线索的剩余部分(一共有 {numStatus} 个状态)", + "thread_follow_with_icon": "{icon} {text}", + "ancestor_follow": "查看这个状态下的别的 {numReplies} 个回复", + "ancestor_follow_with_icon": "{icon} {text}", + "show_all_conversation_with_icon": "{icon} {text}", + "show_all_conversation": "显示完整对话(还有 {numStatus} 个状态)", + "mentions": "提及", + "replies_list_with_others": "回复(另外 +{numReplies} 个):", + "move_down": "把附件右移", + "thread_show_full": "显示这个线索下的所有东西(一共有 {numStatus} 个状态,最大深度 {depth})", + "show_only_conversation_under_this": "只显示这个状态的回复" }, "user_card": { "approve": "核准", @@ -824,7 +907,10 @@ "media_nsfw": "强制设置媒体为敏感内容", "media_removal_desc": "本实例移除来自以下实例的媒体内容:", "ftl_removal_desc": "该实例在从“已知网络”时间线上移除了下列实例:", - "ftl_removal": "从“已知网络”时间线上移除" + "ftl_removal": "从“已知网络”时间线上移除", + "reason": "理由", + "not_applicable": "无", + "instance": "实例" }, "mrf_policies_desc": "MRF 策略会影响本实例的互通行为。以下策略已启用:", "mrf_policies": "已启用的 MRF 策略", diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js index 71f9156a..d7a4430d 100644 --- a/src/lib/notification-i18n-loader.js +++ b/src/lib/notification-i18n-loader.js @@ -3,8 +3,8 @@ // meant to be used to load the partial i18n we need for // the service worker. module.exports = function (source) { - var object = JSON.parse(source) - var smol = { + const object = JSON.parse(source) + const smol = { notifications: object.notifications || {} } diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 24b835da..c73a38ec 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -5,10 +5,12 @@ import { each, get, set, cloneDeep } from 'lodash' let loaded = false const defaultReducer = (state, paths) => ( - paths.length === 0 ? state : paths.reduce((substate, path) => { - set(substate, path, get(state, path)) - return substate - }, {}) + paths.length === 0 + ? state + : paths.reduce((substate, path) => { + set(substate, path, get(state, path)) + return substate + }, {}) ) const saveImmedeatelyActions = [ @@ -30,7 +32,7 @@ export default function createPersistedState ({ key = 'vuex-lz', paths = [], getState = (key, storage) => { - let value = storage.getItem(key) + const value = storage.getItem(key) return value }, setState = (key, state, storage) => { diff --git a/src/modules/api.js b/src/modules/api.js index 54f94356..28f2076e 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -233,7 +233,7 @@ const api = { // Follow requests startFetchingFollowRequests (store) { - if (store.state.fetchers['followRequests']) return + if (store.state.fetchers.followRequests) return const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store }) store.commit('addFetcher', { fetcherName: 'followRequests', fetcher }) @@ -244,7 +244,7 @@ const api = { store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher }) }, removeFollowRequest (store, request) { - let requests = store.state.followRequests.filter((it) => it !== request) + const requests = store.state.followRequests.filter((it) => it !== request) store.commit('setFollowRequests', requests) }, diff --git a/src/modules/config.js b/src/modules/config.js index c4b08390..cc72d60e 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -82,6 +82,8 @@ export const defaultState = { useContainFit: true, disableStickyHeaders: false, showScrollbars: false, + userPopoverZoom: false, + userPopoverOverlay: true, greentext: undefined, // instance default useAtIcon: undefined, // instance default mentionLinkDisplay: undefined, // instance default @@ -146,7 +148,7 @@ const config = { const knownKeys = new Set(Object.keys(defaultState)) const presentKeys = new Set(Object.keys(data)) const intersection = new Set() - for (let elem of presentKeys) { + for (const elem of presentKeys) { if (knownKeys.has(elem)) { intersection.add(elem) } diff --git a/src/modules/errors.js b/src/modules/errors.js index ca89dc0f..d2e24100 100644 --- a/src/modules/errors.js +++ b/src/modules/errors.js @@ -2,8 +2,8 @@ import { capitalize } from 'lodash' export function humanizeErrors (errors) { return Object.entries(errors).reduce((errs, [k, val]) => { - let message = val.reduce((acc, message) => { - let key = capitalize(k.replace(/_/g, ' ')) + const message = val.reduce((acc, message) => { + const key = capitalize(k.replace(/_/g, ' ')) return acc + [key, message].join(' ') + '. ' }, '') return [...errs, message] diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js index 4b73af26..476263bc 100644 --- a/src/modules/serverSideConfig.js +++ b/src/modules/serverSideConfig.js @@ -39,53 +39,53 @@ const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => { * If no api is specified, defaultApi is used (see above) */ export const settingsMap = { - 'defaultScope': 'source.privacy', - 'defaultNSFW': 'source.sensitive', // BROKEN: pleroma/pleroma#2837 - 'stripRichContent': { + defaultScope: 'source.privacy', + defaultNSFW: 'source.sensitive', // BROKEN: pleroma/pleroma#2837 + stripRichContent: { get: 'source.pleroma.no_rich_text', set: 'no_rich_text' }, // Privacy - 'locked': 'locked', - 'acceptChatMessages': { + locked: 'locked', + acceptChatMessages: { get: 'pleroma.accepts_chat_messages', set: 'accepts_chat_messages' }, - 'allowFollowingMove': { + allowFollowingMove: { get: 'pleroma.allow_following_move', set: 'allow_following_move' }, - 'discoverable': { + discoverable: { get: 'source.pleroma.discoverable', set: 'discoverable' }, - 'hideFavorites': { + hideFavorites: { get: 'pleroma.hide_favorites', set: 'hide_favorites' }, - 'hideFollowers': { + hideFollowers: { get: 'pleroma.hide_followers', set: 'hide_followers' }, - 'hideFollows': { + hideFollows: { get: 'pleroma.hide_follows', set: 'hide_follows' }, - 'hideFollowersCount': { + hideFollowersCount: { get: 'pleroma.hide_followers_count', set: 'hide_followers_count' }, - 'hideFollowsCount': { + hideFollowsCount: { get: 'pleroma.hide_follows_count', set: 'hide_follows_count' }, // NotificationSettingsAPIs - 'webPushHideContents': { + webPushHideContents: { get: 'pleroma.notification_settings.hide_notification_contents', set: 'hide_notification_contents', api: notificationsApi }, - 'blockNotificationsFromStrangers': { + blockNotificationsFromStrangers: { get: 'pleroma.notification_settings.block_from_strangers', set: 'block_from_strangers', api: notificationsApi diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 66cc82bc..f8e714cf 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -245,10 +245,10 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us } const processors = { - 'status': (status) => { + status: (status) => { addStatus(status, showImmediately) }, - 'retweet': (status) => { + retweet: (status) => { // RetweetedStatuses are never shown immediately const retweetedStatus = addStatus(status.retweeted_status, false, false) @@ -270,7 +270,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us retweet.retweeted_status = retweetedStatus }, - 'favorite': (favorite) => { + favorite: (favorite) => { // Only update if this is a new favorite. // Ignore our own favorites because we get info about likes as response to like request if (!state.favorites.has(favorite.id)) { @@ -278,7 +278,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us favoriteStatus(favorite) } }, - 'deletion': (deletion) => { + deletion: (deletion) => { const uri = deletion.uri const status = find(allStatuses, { uri }) if (!status) { @@ -292,10 +292,10 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us remove(timelineObject.visibleStatuses, { uri }) } }, - 'follow': (follow) => { + follow: (follow) => { // NOOP, it is known status but we don't do anything about it for now }, - 'default': (unknown) => { + default: (unknown) => { console.log('unknown status type') console.log(unknown) } @@ -303,7 +303,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us each(statuses, (status) => { const type = status.type - const processor = processors[type] || processors['default'] + const processor = processors[type] || processors.default processor(status) }) @@ -345,6 +345,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot } // Only add a new notification if we don't have one for the same action + // eslint-disable-next-line no-prototype-builtins if (!state.notifications.idStore.hasOwnProperty(notification.id)) { updateNotificationsMinMaxId(state, notification) @@ -526,7 +527,7 @@ export const mutations = { }, addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) { const status = state.allStatusesObject[id] - status['emoji_reactions'] = emojiReactions + status.emoji_reactions = emojiReactions }, addOwnReaction (state, { id, emoji, currentUser }) { const status = state.allStatusesObject[id] @@ -547,7 +548,7 @@ export const mutations = { if (reactionIndex >= 0) { status.emoji_reactions[reactionIndex] = newReaction } else { - status['emoji_reactions'] = [...status.emoji_reactions, newReaction] + status.emoji_reactions = [...status.emoji_reactions, newReaction] } }, removeOwnReaction (state, { id, emoji, currentUser }) { @@ -568,7 +569,7 @@ export const mutations = { if (newReaction.count > 0) { status.emoji_reactions[reactionIndex] = newReaction } else { - status['emoji_reactions'] = status.emoji_reactions.filter(r => r.name !== emoji) + status.emoji_reactions = status.emoji_reactions.filter(r => r.name !== emoji) } }, updateStatusWithPoll (state, { id, poll }) { diff --git a/src/modules/users.js b/src/modules/users.js index f951483f..13d4e318 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -103,23 +103,23 @@ export const mutations = { const user = state.usersObject[id] const tags = user.tags || [] const newTags = tags.concat([tag]) - user['tags'] = newTags + user.tags = newTags }, untagUser (state, { user: { id }, tag }) { const user = state.usersObject[id] const tags = user.tags || [] const newTags = tags.filter(t => t !== tag) - user['tags'] = newTags + user.tags = newTags }, updateRight (state, { user: { id }, right, value }) { const user = state.usersObject[id] - let newRights = user.rights + const newRights = user.rights newRights[right] = value - user['rights'] = newRights + user.rights = newRights }, updateActivationStatus (state, { user: { id }, deactivated }) { const user = state.usersObject[id] - user['deactivated'] = deactivated + user.deactivated = deactivated }, setCurrentUser (state, user) { state.lastLoginName = user.screen_name @@ -148,13 +148,13 @@ export const mutations = { clearFriends (state, userId) { const user = state.usersObject[userId] if (user) { - user['friendIds'] = [] + user.friendIds = [] } }, clearFollowers (state, userId) { const user = state.usersObject[userId] if (user) { - user['followerIds'] = [] + user.followerIds = [] } }, addNewUsers (state, users) { @@ -222,7 +222,7 @@ export const mutations = { }, setColor (state, { user: { id }, highlighted }) { const user = state.usersObject[id] - user['highlight'] = highlighted + user.highlight = highlighted }, signUpPending (state) { state.signUpPending = true @@ -393,7 +393,7 @@ const users = { toggleActivationStatus ({ rootState, commit }, { user }) { const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser api({ user }) - .then((user) => { let deactivated = !user.is_active; commit('updateActivationStatus', { user, deactivated }) }) + .then((user) => { const deactivated = !user.is_active; commit('updateActivationStatus', { user, deactivated }) }) }, registerPushNotifications (store) { const token = store.state.currentUser.credentials @@ -457,17 +457,17 @@ const users = { async signUp (store, userInfo) { store.commit('signUpPending') - let rootState = store.rootState + const rootState = store.rootState try { - let data = await rootState.api.backendInteractor.register( + const data = await rootState.api.backendInteractor.register( { params: { ...userInfo } } ) store.commit('signUpSuccess') store.commit('setToken', data.access_token) store.dispatch('loginUser', data.access_token) } catch (e) { - let errors = e.message + const errors = e.message store.commit('signUpFailure', errors) throw e } diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8f09545c..df065cab 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -76,7 +76,7 @@ const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin` const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin` const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` -const MASTODON_SEARCH_2 = `/api/v2/search` +const MASTODON_SEARCH_2 = '/api/v2/search' const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks' const MASTODON_STREAMING = '/api/v1/streaming' @@ -84,7 +84,7 @@ const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers' const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions` const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` -const PLEROMA_CHATS_URL = `/api/v1/pleroma/chats` +const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}` const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages` const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read` @@ -94,7 +94,7 @@ const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' const oldfetch = window.fetch -let fetch = (url, options) => { +const fetch = (url, options) => { options = options || {} const baseUrl = '' const fullUrl = baseUrl + url @@ -106,7 +106,7 @@ const promisedRequest = ({ method, url, params, payload, credentials, headers = const options = { method, headers: { - 'Accept': 'application/json', + Accept: 'application/json', 'Content-Type': 'application/json', ...headers } @@ -230,16 +230,16 @@ const getCaptcha = () => fetch('/api/pleroma/captcha').then(resp => resp.json()) const authHeaders = (accessToken) => { if (accessToken) { - return { 'Authorization': `Bearer ${accessToken}` } + return { Authorization: `Bearer ${accessToken}` } } else { return { } } } const followUser = ({ id, credentials, ...options }) => { - let url = MASTODON_FOLLOW_URL(id) + const url = MASTODON_FOLLOW_URL(id) const form = {} - if (options.reblogs !== undefined) { form['reblogs'] = options.reblogs } + if (options.reblogs !== undefined) { form.reblogs = options.reblogs } return fetch(url, { body: JSON.stringify(form), headers: { @@ -251,7 +251,7 @@ const followUser = ({ id, credentials, ...options }) => { } const unfollowUser = ({ id, credentials }) => { - let url = MASTODON_UNFOLLOW_URL(id) + const url = MASTODON_UNFOLLOW_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -293,7 +293,7 @@ const unblockUser = ({ id, credentials }) => { } const approveUser = ({ id, credentials }) => { - let url = MASTODON_APPROVE_USER_URL(id) + const url = MASTODON_APPROVE_USER_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -301,7 +301,7 @@ const approveUser = ({ id, credentials }) => { } const denyUser = ({ id, credentials }) => { - let url = MASTODON_DENY_USER_URL(id) + const url = MASTODON_DENY_USER_URL(id) return fetch(url, { headers: authHeaders(credentials), method: 'POST' @@ -309,13 +309,13 @@ const denyUser = ({ id, credentials }) => { } const fetchUser = ({ id, credentials }) => { - let url = `${MASTODON_USER_URL}/${id}` + const url = `${MASTODON_USER_URL}/${id}` return promisedRequest({ url, credentials }) .then((data) => parseUser(data)) } const fetchUserRelationship = ({ id, credentials }) => { - let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` + const url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` return fetch(url, { headers: authHeaders(credentials) }) .then((response) => { return new Promise((resolve, reject) => response.json() @@ -334,7 +334,7 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { maxId && `max_id=${maxId}`, sinceId && `since_id=${sinceId}`, limit && `limit=${limit}`, - `with_relationships=true` + 'with_relationships=true' ].filter(_ => _).join('&') url = url + (args ? '?' + args : '') @@ -344,6 +344,7 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { } const exportFriends = ({ id, credentials }) => { + // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { try { let friends = [] @@ -369,7 +370,7 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { maxId && `max_id=${maxId}`, sinceId && `since_id=${sinceId}`, limit && `limit=${limit}`, - `with_relationships=true` + 'with_relationships=true' ].filter(_ => _).join('&') url += args ? '?' + args : '' @@ -386,7 +387,7 @@ const fetchFollowRequests = ({ credentials }) => { } const fetchConversation = ({ id, credentials }) => { - let urlContext = MASTODON_STATUS_CONTEXT_URL(id) + const urlContext = MASTODON_STATUS_CONTEXT_URL(id) return fetch(urlContext, { headers: authHeaders(credentials) }) .then((data) => { if (data.ok) { @@ -402,7 +403,7 @@ const fetchConversation = ({ id, credentials }) => { } const fetchStatus = ({ id, credentials }) => { - let url = MASTODON_STATUS_URL(id) + const url = MASTODON_STATUS_URL(id) return fetch(url, { headers: authHeaders(credentials) }) .then((data) => { if (data.ok) { @@ -426,7 +427,7 @@ const tagUser = ({ tag, credentials, user }) => { return fetch(TAG_USER_URL, { method: 'PUT', - headers: headers, + headers, body: JSON.stringify(form) }) } @@ -443,7 +444,7 @@ const untagUser = ({ tag, credentials, user }) => { return fetch(TAG_USER_URL, { method: 'DELETE', - headers: headers, + headers, body: JSON.stringify(body) }) } @@ -496,7 +497,7 @@ const deleteUser = ({ credentials, user }) => { return fetch(`${ADMIN_USERS_URL}?nickname=${screenName}`, { method: 'DELETE', - headers: headers + headers }) } @@ -516,7 +517,7 @@ const fetchTimeline = ({ friends: MASTODON_USER_HOME_TIMELINE_URL, dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, notifications: MASTODON_USER_NOTIFICATIONS_URL, - 'publicAndExternal': MASTODON_PUBLIC_TIMELINE, + publicAndExternal: MASTODON_PUBLIC_TIMELINE, user: MASTODON_USER_TIMELINE_URL, media: MASTODON_USER_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, @@ -695,7 +696,7 @@ const postStatus = ({ form.append('preview', 'true') } - let postHeaders = authHeaders(credentials) + const postHeaders = authHeaders(credentials) if (idempotencyKey) { postHeaders['idempotency-key'] = idempotencyKey } @@ -1000,7 +1001,7 @@ const vote = ({ pollId, choices, credentials }) => { method: 'POST', credentials, payload: { - choices: choices + choices } }) } @@ -1060,8 +1061,8 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { url: MASTODON_REPORT_USER_URL, method: 'POST', payload: { - 'account_id': userId, - 'status_ids': statusIds, + account_id: userId, + status_ids: statusIds, comment, forward }, @@ -1083,7 +1084,7 @@ const searchUsers = ({ credentials, query }) => { const search2 = ({ credentials, q, resolve, limit, offset, following }) => { let url = MASTODON_SEARCH_2 - let params = [] + const params = [] if (q) { params.push(['q', encodeURIComponent(q)]) @@ -1107,7 +1108,7 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { params.push(['with_relationships', true]) - let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') + const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') url += `?${queryString}` return fetch(url, { headers: authHeaders(credentials) }) @@ -1261,12 +1262,12 @@ export const handleMastoWS = (wsEvent) => { } export const WSConnectionStatus = Object.freeze({ - 'JOINED': 1, - 'CLOSED': 2, - 'ERROR': 3, - 'DISABLED': 4, - 'STARTING': 5, - 'STARTING_INITIAL': 6 + JOINED: 1, + CLOSED: 2, + ERROR: 3, + DISABLED: 4, + STARTING: 5, + STARTING_INITIAL: 6 }) const chats = ({ credentials }) => { @@ -1304,11 +1305,11 @@ const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => { const sendChatMessage = ({ id, content, mediaId = null, idempotencyKey, credentials }) => { const payload = { - 'content': content + content } if (mediaId) { - payload['media_id'] = mediaId + payload.media_id = mediaId } const headers = {} @@ -1320,7 +1321,7 @@ const sendChatMessage = ({ id, content, mediaId = null, idempotencyKey, credenti return promisedRequest({ url: PLEROMA_CHAT_MESSAGES_URL(id), method: 'POST', - payload: payload, + payload, credentials, headers }) @@ -1331,7 +1332,7 @@ const readChat = ({ id, lastReadId, credentials }) => { url: PLEROMA_CHAT_READ_URL(id), method: 'POST', payload: { - 'last_read_id': lastReadId + last_read_id: lastReadId }, credentials }) diff --git a/src/services/chat_service/chat_service.js b/src/services/chat_service/chat_service.js index 92ff689d..eb26a0ab 100644 --- a/src/services/chat_service/chat_service.js +++ b/src/services/chat_service/chat_service.js @@ -7,7 +7,7 @@ const empty = (chatId) => { messages: [], newMessageCount: 0, lastSeenMessageId: '0', - chatId: chatId, + chatId, minId: undefined, maxId: undefined } @@ -101,7 +101,7 @@ const add = (storage, { messages: newMessages, updateMaxId = true }) => { storage.messages = storage.messages.filter(msg => msg.id !== message.id) } Object.assign(fakeMessage, message, { error: false }) - delete fakeMessage['fakeId'] + delete fakeMessage.fakeId storage.idIndex[fakeMessage.id] = fakeMessage delete storage.idIndex[message.fakeId] @@ -178,7 +178,7 @@ const getView = (storage) => { id: date.getTime().toString() }) - previousMessage['isTail'] = true + previousMessage.isTail = true currentMessageChainId = undefined afterDate = true } @@ -193,15 +193,15 @@ const getView = (storage) => { // end a message chian if ((nextMessage && nextMessage.account_id) !== message.account_id) { - object['isTail'] = true + object.isTail = true currentMessageChainId = undefined } // start a new message chain if ((previousMessage && previousMessage.data && previousMessage.data.account_id) !== message.account_id || afterDate) { currentMessageChainId = _.uniqueId() - object['isHead'] = true - object['messageChainId'] = currentMessageChainId + object.isHead = true + object.messageChainId = currentMessageChainId } result.push(object) diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js index de6e0625..a8da1eed 100644 --- a/src/services/chat_utils/chat_utils.js +++ b/src/services/chat_utils/chat_utils.js @@ -25,7 +25,7 @@ export const buildFakeMessage = ({ content, chatId, attachments, userId, idempot chat_id: chatId, created_at: new Date(), id: `${new Date().getTime()}`, - attachments: attachments, + attachments, account_id: userId, idempotency_key: idempotencyKey, emojis: [], diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index ec104269..47d6344e 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -144,11 +144,13 @@ export const invert = (rgb) => { */ export const hex2rgb = (hex) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : null + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } + : null } /** diff --git a/src/services/completion/completion.js b/src/services/completion/completion.js index 8a6eba7e..8fa4f75b 100644 --- a/src/services/completion/completion.js +++ b/src/services/completion/completion.js @@ -35,7 +35,7 @@ export const addPositionToWords = (words) => { } export const splitByWhitespaceBoundary = (str) => { - let result = [] + const result = [] let currentWord = '' for (let i = 0; i < str.length; i++) { const currentChar = str[i] diff --git a/src/services/date_utils/date_utils.js b/src/services/date_utils/date_utils.js index 677c184c..c93d2176 100644 --- a/src/services/date_utils/date_utils.js +++ b/src/services/date_utils/date_utils.js @@ -10,7 +10,7 @@ export const relativeTime = (date, nowThreshold = 1) => { if (typeof date === 'string') date = Date.parse(date) const round = Date.now() > date ? Math.floor : Math.ceil const d = Math.abs(Date.now() - date) - let r = { num: round(d / YEAR), key: 'time.unit.years' } + const r = { num: round(d / YEAR), key: 'time.unit.years' } if (d < nowThreshold * SECOND) { r.num = 0 r.key = 'time.now' diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 0005e95a..9000e81a 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -39,9 +39,9 @@ const qvitterStatusType = (status) => { export const parseUser = (data) => { const output = {} - const masto = data.hasOwnProperty('acct') + const masto = Object.prototype.hasOwnProperty.call(data, 'acct') // case for users in "mentions" property for statuses in MastoAPI - const mastoShort = masto && !data.hasOwnProperty('avatar') + const mastoShort = masto && !Object.prototype.hasOwnProperty.call(data, 'avatar') output.id = String(data.id) output._original = data // used for server-side settings @@ -225,7 +225,7 @@ export const parseUser = (data) => { export const parseAttachment = (data) => { const output = {} - const masto = !data.hasOwnProperty('oembed') + const masto = !Object.prototype.hasOwnProperty.call(data, 'oembed') if (masto) { // Not exactly same... @@ -246,7 +246,7 @@ export const parseAttachment = (data) => { export const parseStatus = (data) => { const output = {} - const masto = data.hasOwnProperty('account') + const masto = Object.prototype.hasOwnProperty.call(data, 'account') if (masto) { output.favorited = data.favourited @@ -371,10 +371,10 @@ export const parseStatus = (data) => { export const parseNotification = (data) => { const mastoDict = { - 'favourite': 'like', - 'reblog': 'repeat' + favourite: 'like', + reblog: 'repeat' } - const masto = !data.hasOwnProperty('ntype') + const masto = !Object.prototype.hasOwnProperty.call(data, 'ntype') const output = {} if (masto) { diff --git a/src/services/errors/errors.js b/src/services/errors/errors.js index d4cf9132..50372e5e 100644 --- a/src/services/errors/errors.js +++ b/src/services/errors/errors.js @@ -26,6 +26,7 @@ export class RegistrationError extends Error { // the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors if (typeof error === 'string') { error = JSON.parse(error) + // eslint-disable-next-line if (error.hasOwnProperty('error')) { error = JSON.parse(error.error) } diff --git a/src/services/file_size_format/file_size_format.js b/src/services/file_size_format/file_size_format.js index 7e6cd4d7..17deb09b 100644 --- a/src/services/file_size_format/file_size_format.js +++ b/src/services/file_size_format/file_size_format.js @@ -1,15 +1,14 @@ -const fileSizeFormat = (num) => { - var exponent - var unit - var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'] +const fileSizeFormat = (numArg) => { + const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'] + let num = numArg if (num < 1) { return num + ' ' + units[0] } - exponent = Math.min(Math.floor(Math.log(num) / Math.log(1024)), units.length - 1) + const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1024)), units.length - 1) num = (num / Math.pow(1024, exponent)).toFixed(2) * 1 - unit = units[exponent] - return { num: num, unit: unit } + const unit = units[exponent] + return { num, unit } } const fileSizeFormatService = { fileSizeFormat diff --git a/src/services/html_converter/html_line_converter.service.js b/src/services/html_converter/html_line_converter.service.js index 5eeaa7cb..9c3d1f19 100644 --- a/src/services/html_converter/html_line_converter.service.js +++ b/src/services/html_converter/html_line_converter.service.js @@ -46,7 +46,7 @@ export const convertHtmlToLines = (html = '') => { // All block-level elements that aren't empty elements, i.e. not <hr> const nonEmptyElements = new Set(visualLineElements) // Difference - for (let elem of emptyElements) { + for (const elem of emptyElements) { nonEmptyElements.delete(elem) } @@ -56,7 +56,7 @@ export const convertHtmlToLines = (html = '') => { ...emptyElements.values() ]) - let buffer = [] // Current output buffer + const buffer = [] // Current output buffer const level = [] // How deep we are in tags and which tags were there let textBuffer = '' // Current line content let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag diff --git a/src/services/html_converter/utility.service.js b/src/services/html_converter/utility.service.js index 4d0c36c2..583ccca5 100644 --- a/src/services/html_converter/utility.service.js +++ b/src/services/html_converter/utility.service.js @@ -50,7 +50,7 @@ export const processTextForEmoji = (text, emojis, processor) => { if (char === ':') { const next = text.slice(i + 1) let found = false - for (let emoji of emojis) { + for (const emoji of emojis) { if (next.slice(0, emoji.shortcode.length + 1) === (emoji.shortcode + ':')) { found = emoji break diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js index 8cef2522..d3389785 100644 --- a/src/services/locale/locale.service.js +++ b/src/services/locale/locale.service.js @@ -3,9 +3,9 @@ import ISO6391 from 'iso-639-1' import _ from 'lodash' const specialLanguageCodes = { - 'ja_easy': 'ja', - 'zh_Hant': 'zh-HANT', - 'zh': 'zh-Hans' + ja_easy: 'ja', + zh_Hant: 'zh-HANT', + zh: 'zh-Hans' } const internalToBrowserLocale = code => specialLanguageCodes[code] || code @@ -14,16 +14,16 @@ const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_ const getLanguageName = (code) => { const specialLanguageNames = { - 'ja_easy': 'やさしいにほんご', - 'zh': '简体中文', - 'zh_Hant': '繁體中文' + ja_easy: 'やさしいにほんご', + zh: '简体中文', + zh_Hant: '繁體中文' } const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code) const browserLocale = internalToBrowserLocale(code) return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1) } -const languages = _.map(languagesObject.languages, (code) => ({ code: code, name: getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name)) +const languages = _.map(languagesObject.languages, (code) => ({ code, name: getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name)) const localeService = { internalToBrowserLocale, diff --git a/src/services/new_api/password_reset.js b/src/services/new_api/password_reset.js index 43199625..9f3c27b5 100644 --- a/src/services/new_api/password_reset.js +++ b/src/services/new_api/password_reset.js @@ -1,6 +1,6 @@ import { reduce } from 'lodash' -const MASTODON_PASSWORD_RESET_URL = `/auth/password` +const MASTODON_PASSWORD_RESET_URL = '/auth/password' const resetPassword = ({ instance, email }) => { const params = { email } diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index cb241e33..95138a16 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -25,20 +25,20 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { const hideMutedPosts = getters.mergedConfig.hideMutedPosts args['includeTypes'] = mastoApiNotificationTypes - args['withMuted'] = !hideMutedPosts + args.withMuted = !hideMutedPosts - args['timeline'] = 'notifications' + args.timeline = 'notifications' if (older) { if (timelineData.minId !== Number.POSITIVE_INFINITY) { - args['until'] = timelineData.minId + args.until = timelineData.minId } return fetchNotifications({ store, args, older }) } else { // fetch new notifications if (since === undefined && timelineData.maxId !== Number.POSITIVE_INFINITY) { - args['since'] = timelineData.maxId + args.since = timelineData.maxId } else if (since !== null) { - args['since'] = since + args.since = since } const result = fetchNotifications({ store, args, older }) @@ -51,7 +51,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { const readNotifsIds = notifications.filter(n => n.seen).map(n => n.id) const numUnseenNotifs = notifications.length - readNotifsIds.length if (numUnseenNotifs > 0 && readNotifsIds.length > 0) { - args['since'] = Math.max(...readNotifsIds) + args.since = Math.max(...readNotifsIds) fetchNotifications({ store, args, older }) } diff --git a/src/services/push/push.js b/src/services/push/push.js index 5836fc26..40d7b0fd 100644 --- a/src/services/push/push.js +++ b/src/services/push/push.js @@ -43,7 +43,7 @@ function deleteSubscriptionFromBackEnd (token) { method: 'DELETE', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` + Authorization: `Bearer ${token}` } }).then((response) => { if (!response.ok) throw new Error('Bad status code from server.') @@ -56,7 +56,7 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility) method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` + Authorization: `Bearer ${token}` }, body: JSON.stringify({ subscription, diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index b619f810..b376ef4d 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -39,7 +39,7 @@ import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js' export const CURRENT_VERSION = 3 export const getLayersArray = (layer, data = LAYERS) => { - let array = [layer] + const array = [layer] let parent = data[layer] while (parent) { array.unshift(parent) @@ -138,6 +138,7 @@ export const topoSort = ( if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return ai - bi if (depsA === 0 && depsB !== 0) return -1 if (depsB === 0 && depsA !== 0) return 1 + return 0 // failsafe, shouldn't happen? }).map(({ data }) => data) } diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 46bba41a..7ba138e0 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -34,20 +34,20 @@ const fetchAndUpdate = ({ const loggedIn = !!rootState.users.currentUser if (older) { - args['until'] = until || timelineData.minId + args.until = until || timelineData.minId } else { if (since === undefined) { - args['since'] = timelineData.maxId + args.since = timelineData.maxId } else if (since !== null) { - args['since'] = since + args.since = since } } - args['userId'] = userId - args['tag'] = tag - args['withMuted'] = !hideMutedPosts + args.userId = userId + args.tag = tag + args.withMuted = !hideMutedPosts if (loggedIn && ['friends', 'public', 'publicAndExternal'].includes(timeline)) { - args['replyVisibility'] = replyVisibility + args.replyVisibility = replyVisibility } const numStatusesBeforeFetch = timelineData.statuses.length @@ -60,7 +60,7 @@ const fetchAndUpdate = ({ const { data: statuses, pagination } = response if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) { - store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId }) + store.dispatch('queueFlush', { timeline, id: timelineData.maxId }) } update({ store, statuses, timeline, showImmediately, userId, pagination }) return { statuses, pagination } diff --git a/src/services/user_highlighter/user_highlighter.js b/src/services/user_highlighter/user_highlighter.js index 3b07592e..b5f58040 100644 --- a/src/services/user_highlighter/user_highlighter.js +++ b/src/services/user_highlighter/user_highlighter.js @@ -36,7 +36,7 @@ const highlightStyle = (prefs) => { 'linear-gradient(to right,', `${solidColor} ,`, `${solidColor} 2px,`, - `transparent 6px` + 'transparent 6px' ].join(' '), backgroundPosition: '0 0', ...customProps @@ -57,8 +57,8 @@ self.addEventListener('notificationclick', (event) => { event.notification.close() event.waitUntil(getWindowClients().then((list) => { - for (var i = 0; i < list.length; i++) { - var client = list[i] + for (let i = 0; i < list.length; i++) { + const client = list[i] if (client.url === '/' && 'focus' in client) { return client.focus() } } |
