aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSean King <seanking2919@protonmail.com>2022-08-01 18:17:09 -0600
committerSean King <seanking2919@protonmail.com>2022-08-01 18:17:09 -0600
commit081aa0fd0552732f208bd35e37cde06c66536791 (patch)
tree053827cd9d9b258d6752195ab09eabd37d4c813b /src
parent75216c5feb32b32e24952663ffa4233410585785 (diff)
parent3fc9673a7d0fb851283e4ed687c2fd7790f03317 (diff)
Fix merge conflicts
Diffstat (limited to 'src')
-rw-r--r--src/App.js3
-rw-r--r--src/App.scss11
-rw-r--r--src/App.vue23
-rw-r--r--src/boot/after_store.js8
-rw-r--r--src/boot/routes.js9
-rw-r--r--src/components/about/about.vue2
-rw-r--r--src/components/account_actions/account_actions.vue4
-rw-r--r--src/components/avatar_list/avatar_list.vue2
-rw-r--r--src/components/basic_user_card/basic_user_card.js12
-rw-r--r--src/components/basic_user_card/basic_user_card.vue32
-rw-r--r--src/components/chat/chat.js2
-rw-r--r--src/components/chat_list/chat_list.vue2
-rw-r--r--src/components/chat_message/chat_message.js8
-rw-r--r--src/components/chat_message/chat_message.vue16
-rw-r--r--src/components/chat_title/chat_title.js10
-rw-r--r--src/components/chat_title/chat_title.vue8
-rw-r--r--src/components/checkbox/checkbox.vue4
-rw-r--r--src/components/color_input/color_input.vue2
-rw-r--r--src/components/conversation/conversation.js2
-rw-r--r--src/components/conversation/conversation.vue2
-rw-r--r--src/components/desktop_nav/desktop_nav.js24
-rw-r--r--src/components/desktop_nav/desktop_nav.scss1
-rw-r--r--src/components/desktop_nav/desktop_nav.vue2
-rw-r--r--src/components/domain_mute_card/domain_mute_card.vue4
-rw-r--r--src/components/emoji_input/emoji_input.js2
-rw-r--r--src/components/emoji_picker/emoji_picker.js2
-rw-r--r--src/components/emoji_picker/emoji_picker.scss3
-rw-r--r--src/components/emoji_reactions/emoji_reactions.vue2
-rw-r--r--src/components/extra_buttons/extra_buttons.js5
-rw-r--r--src/components/extra_buttons/extra_buttons.vue56
-rw-r--r--src/components/favorite_button/favorite_button.vue2
-rw-r--r--src/components/features_panel/features_panel.vue2
-rw-r--r--src/components/flash/flash.js2
-rw-r--r--src/components/font_control/font_control.vue2
-rw-r--r--src/components/global_notice_list/global_notice_list.vue2
-rw-r--r--src/components/hashtag_link/hashtag_link.vue4
-rw-r--r--src/components/image_cropper/image_cropper.js2
-rw-r--r--src/components/instance_specific_panel/instance_specific_panel.vue2
-rw-r--r--src/components/interactions/interactions.js2
-rw-r--r--src/components/interface_language_switcher/interface_language_switcher.vue1
-rw-r--r--src/components/login_form/login_form.js2
-rw-r--r--src/components/login_form/login_form.vue2
-rw-r--r--src/components/media_modal/media_modal.vue2
-rw-r--r--src/components/media_upload/media_upload.js5
-rw-r--r--src/components/media_upload/media_upload.vue2
-rw-r--r--src/components/mention_link/mention_link.js24
-rw-r--r--src/components/mention_link/mention_link.scss29
-rw-r--r--src/components/mention_link/mention_link.vue91
-rw-r--r--src/components/mentions_line/mentions_line.vue17
-rw-r--r--src/components/mfa_form/recovery_form.vue2
-rw-r--r--src/components/mobile_nav/mobile_nav.vue11
-rw-r--r--src/components/modal/modal.vue7
-rw-r--r--src/components/moderation_tools/moderation_tools.vue8
-rw-r--r--src/components/mrf_transparency_panel/mrf_transparency_panel.js6
-rw-r--r--src/components/nav_panel/nav_panel.vue4
-rw-r--r--src/components/notification/notification.js6
-rw-r--r--src/components/notification/notification.vue25
-rw-r--r--src/components/notifications/notification_filters.vue4
-rw-r--r--src/components/notifications/notifications.js10
-rw-r--r--src/components/notifications/notifications.vue5
-rw-r--r--src/components/poll/poll_form.vue2
-rw-r--r--src/components/popover/popover.js245
-rw-r--r--src/components/popover/popover.vue48
-rw-r--r--src/components/post_status_form/post_status_form.js16
-rw-r--r--src/components/react_button/react_button.js2
-rw-r--r--src/components/react_button/react_button.vue13
-rw-r--r--src/components/registration/registration.vue9
-rw-r--r--src/components/remote_follow/remote_follow.js2
-rw-r--r--src/components/retweet_button/retweet_button.vue2
-rw-r--r--src/components/search_bar/search_bar.js2
-rw-r--r--src/components/selectable_list/selectable_list.vue4
-rw-r--r--src/components/settings_modal/helpers/modified_indicator.vue14
-rw-r--r--src/components/settings_modal/helpers/server_side_indicator.vue14
-rw-r--r--src/components/settings_modal/settings_modal.vue4
-rw-r--r--src/components/settings_modal/tabs/general_tab.vue36
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.vue34
-rw-r--r--src/components/settings_modal/tabs/profile_tab.js10
-rw-r--r--src/components/settings_modal/tabs/profile_tab.vue2
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa.js6
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa_totp.js2
-rw-r--r--src/components/settings_modal/tabs/security_tab/security_tab.js2
-rw-r--r--src/components/settings_modal/tabs/theme_tab/preview.vue5
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.js8
-rw-r--r--src/components/shadow_control/shadow_control.js8
-rw-r--r--src/components/shadow_control/shadow_control.vue2
-rw-r--r--src/components/shout_panel/shout_panel.js2
-rw-r--r--src/components/shout_panel/shout_panel.vue2
-rw-r--r--src/components/side_drawer/side_drawer.js2
-rw-r--r--src/components/side_drawer/side_drawer.vue4
-rw-r--r--src/components/staff_panel/staff_panel.js4
-rw-r--r--src/components/staff_panel/staff_panel.vue2
-rw-r--r--src/components/status/status.js13
-rw-r--r--src/components/status/status.vue31
-rw-r--r--src/components/status_body/status_body.vue2
-rw-r--r--src/components/status_content/status_content.vue2
-rw-r--r--src/components/status_popover/status_popover.js7
-rw-r--r--src/components/status_popover/status_popover.vue10
-rw-r--r--src/components/sticker_picker/sticker_picker.js4
-rw-r--r--src/components/terms_of_service_panel/terms_of_service_panel.vue2
-rw-r--r--src/components/timeline/timeline.vue5
-rw-r--r--src/components/timeline/timeline_quick_settings.vue4
-rw-r--r--src/components/timeline_menu/timeline_menu.js6
-rw-r--r--src/components/timeline_menu/timeline_menu.vue56
-rw-r--r--src/components/timeline_menu/timeline_menu_content.vue2
-rw-r--r--src/components/user_card/user_card.js32
-rw-r--r--src/components/user_card/user_card.scss51
-rw-r--r--src/components/user_card/user_card.vue50
-rw-r--r--src/components/user_list_popover/user_list_popover.vue6
-rw-r--r--src/components/user_popover/user_popover.js23
-rw-r--r--src/components/user_popover/user_popover.vue33
-rw-r--r--src/components/user_profile/user_profile.vue17
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.vue2
-rw-r--r--src/components/who_to_follow/who_to_follow.js2
-rw-r--r--src/components/who_to_follow_panel/who_to_follow_panel.js10
-rw-r--r--src/components/who_to_follow_panel/who_to_follow_panel.vue2
-rw-r--r--src/i18n/en.json7
-rw-r--r--src/i18n/fr.json159
-rw-r--r--src/i18n/messages.js2
-rw-r--r--src/i18n/zh.json104
-rw-r--r--src/lib/notification-i18n-loader.js4
-rw-r--r--src/lib/persisted_state.js12
-rw-r--r--src/modules/api.js4
-rw-r--r--src/modules/config.js4
-rw-r--r--src/modules/errors.js4
-rw-r--r--src/modules/serverSideConfig.js28
-rw-r--r--src/modules/statuses.js23
-rw-r--r--src/modules/users.js24
-rw-r--r--src/services/api/api.service.js73
-rw-r--r--src/services/chat_service/chat_service.js12
-rw-r--r--src/services/chat_utils/chat_utils.js2
-rw-r--r--src/services/color_convert/color_convert.js12
-rw-r--r--src/services/completion/completion.js2
-rw-r--r--src/services/date_utils/date_utils.js2
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js14
-rw-r--r--src/services/errors/errors.js1
-rw-r--r--src/services/export_import/export_import.js4
-rw-r--r--src/services/file_size_format/file_size_format.js13
-rw-r--r--src/services/html_converter/html_line_converter.service.js4
-rw-r--r--src/services/html_converter/utility.service.js2
-rw-r--r--src/services/locale/locale.service.js14
-rw-r--r--src/services/new_api/password_reset.js2
-rw-r--r--src/services/notifications_fetcher/notifications_fetcher.service.js12
-rw-r--r--src/services/push/push.js4
-rw-r--r--src/services/theme_data/theme_data.service.js3
-rw-r--r--src/services/timeline_fetcher/timeline_fetcher.service.js16
-rw-r--r--src/services/user_highlighter/user_highlighter.js2
-rw-r--r--src/sw.js4
147 files changed, 1287 insertions, 712 deletions
diff --git a/src/App.js b/src/App.js
index f87b3962..0b63b242 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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'
@@ -34,7 +33,7 @@ export default {
MobilePostStatusButton,
MobileNav,
DesktopNav,
- SettingsModal,
+ SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
UserReportingModal,
PostStatusModal,
EditStatusModal,
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 d36f8fe1..9869e835 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"
@@ -57,6 +69,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 55e46c85..0f55accc 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()
}
@@ -320,6 +320,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 {
@@ -397,6 +398,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 f8df9eb5..47509e86 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -276,7 +276,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 ef040d26..8c6aca9b 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -27,7 +27,7 @@ library.add(
)
const ExtraButtons = {
- props: [ 'status' ],
+ props: ['status'],
components: { Popover },
methods: {
deleteStatus () {
@@ -110,6 +110,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 eda838dc..b3e37dff 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="ownStatus && editingAvailable"
class="button-default dropdown-item dropdown-item-icon"
@@ -140,18 +142,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 c5ceb63d..cc6a15e1 100644
--- a/src/components/interactions/interactions.js
+++ b/src/components/interactions/interactions.js
@@ -12,7 +12,7 @@ const Interactions = {
data () {
return {
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
- filterMode: tabModeDict['mentions']
+ filterMode: tabModeDict.mentions
}
},
methods: {
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 398bb7a9..882b68f9 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -5,6 +5,7 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
+import 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'
@@ -39,14 +40,15 @@ const Notification = {
unmuted: false
}
},
- props: [ 'notification' ],
+ props: ['notification'],
components: {
StatusContent,
UserAvatar,
UserCard,
Timeago,
Status,
- RichContent
+ RichContent,
+ UserPopover
},
methods: {
toggleUserExpanded () {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 7d3d0c69..d2b903f6 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 ae81bfa6..77f73d04 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}`
})
@@ -270,7 +270,7 @@ const PostStatusForm = {
})
},
watch: {
- 'newStatus': {
+ newStatus: {
deep: true,
handler () {
this.statusChanged()
@@ -301,7 +301,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
@@ -420,7 +420,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')
},
@@ -490,7 +490,7 @@ const PostStatusForm = {
},
onEmojiInputInput (e) {
this.$nextTick(() => {
- this.resize(this.$refs['textarea'])
+ this.resize(this.$refs.textarea)
})
},
resize (e) {
@@ -505,8 +505,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
@@ -592,7 +592,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>
&nbsp;
<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>
&nbsp;
<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 b7f20374..99865204 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 () {
@@ -454,7 +455,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)
@@ -469,7 +470,7 @@ const Status = {
}
},
watch: {
- 'highlight': function (id) {
+ highlight: function (id) {
this.scrollIfHighlighted(id)
},
'status.repeat_num': function (num) {
@@ -484,7 +485,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 00462f0a..a13e5ab0 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)"
@@ -516,6 +515,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 a491a808..e176c1af 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -237,8 +237,9 @@
}
},
"registration": {
- "bio": "Bio",
+ "bio_optional": "Bio (optional)",
"email": "Email",
+ "email_optional": "Email (optional)",
"fullname": "Display name",
"password_confirm": "Password confirmation",
"registration": "Registration",
@@ -549,10 +550,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 d123dd7e..2970cd3d 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -240,7 +240,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 })
@@ -251,7 +251,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 6ae2e754..eaf67a91 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -81,6 +81,8 @@ export const defaultState = {
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
+ userPopoverZoom: false,
+ userPopoverOverlay: true,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
@@ -145,7 +147,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 a2e27b87..57b430c3 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -245,13 +245,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}
const processors = {
- 'status': (status) => {
+ status: (status) => {
addStatus(status, showImmediately)
},
- 'edit': (status) => {
+ edit: (status) => {
addStatus(status, showImmediately)
},
- 'retweet': (status) => {
+ retweet: (status) => {
// RetweetedStatuses are never shown immediately
const retweetedStatus = addStatus(status.retweeted_status, false, false)
@@ -273,7 +273,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)) {
@@ -281,7 +281,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) {
@@ -295,10 +295,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)
}
@@ -306,7 +306,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)
})
@@ -344,6 +344,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)
@@ -525,7 +526,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]
@@ -546,7 +547,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 }) {
@@ -567,7 +568,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 d3f203a7..41c14596 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -78,7 +78,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'
@@ -86,7 +86,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`
@@ -95,7 +95,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
@@ -107,7 +107,7 @@ const promisedRequest = ({ method, url, params, payload, credentials, headers =
const options = {
method,
headers: {
- 'Accept': 'application/json',
+ Accept: 'application/json',
'Content-Type': 'application/json',
...headers
}
@@ -231,16 +231,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: {
@@ -252,7 +252,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'
@@ -294,7 +294,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'
@@ -302,7 +302,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'
@@ -310,13 +310,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()
@@ -335,7 +335,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 : '')
@@ -345,6 +345,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 = []
@@ -370,7 +371,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 : ''
@@ -387,7 +388,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) {
@@ -403,7 +404,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) {
@@ -452,7 +453,7 @@ const tagUser = ({ tag, credentials, user }) => {
return fetch(TAG_USER_URL, {
method: 'PUT',
- headers: headers,
+ headers,
body: JSON.stringify(form)
})
}
@@ -469,7 +470,7 @@ const untagUser = ({ tag, credentials, user }) => {
return fetch(TAG_USER_URL, {
method: 'DELETE',
- headers: headers,
+ headers,
body: JSON.stringify(body)
})
}
@@ -522,7 +523,7 @@ const deleteUser = ({ credentials, user }) => {
return fetch(`${ADMIN_USERS_URL}?nickname=${screenName}`, {
method: 'DELETE',
- headers: headers
+ headers
})
}
@@ -541,7 +542,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,
@@ -715,7 +716,7 @@ const postStatus = ({
form.append('preview', 'true')
}
- let postHeaders = authHeaders(credentials)
+ const postHeaders = authHeaders(credentials)
if (idempotencyKey) {
postHeaders['idempotency-key'] = idempotencyKey
}
@@ -1068,7 +1069,7 @@ const vote = ({ pollId, choices, credentials }) => {
method: 'POST',
credentials,
payload: {
- choices: choices
+ choices
}
})
}
@@ -1128,8 +1129,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
},
@@ -1151,7 +1152,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)])
@@ -1175,7 +1176,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) })
@@ -1332,12 +1333,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 }) => {
@@ -1375,11 +1376,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 = {}
@@ -1391,7 +1392,7 @@ const sendChatMessage = ({ id, content, mediaId = null, idempotencyKey, credenti
return promisedRequest({
url: PLEROMA_CHAT_MESSAGES_URL(id),
method: 'POST',
- payload: payload,
+ payload,
credentials,
headers
})
@@ -1402,7 +1403,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 2a186ba1..c00e9796 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...
@@ -256,7 +256,7 @@ export const parseSource = (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
@@ -387,10 +387,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/export_import/export_import.js b/src/services/export_import/export_import.js
index ac67cf9c..7fee0ad3 100644
--- a/src/services/export_import/export_import.js
+++ b/src/services/export_import/export_import.js
@@ -1,9 +1,11 @@
+import utf8 from 'utf8'
+
export const newExporter = ({
filename = 'data',
getExportedObject
}) => ({
exportData () {
- const stringified = JSON.stringify(getExportedObject(), null, 2) // Pretty-print and indent with 2 spaces
+ const stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
// Create an invisible link with a data url and simulate a click
const e = document.createElement('a')
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 f83f871e..c0e299e4 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -12,20 +12,20 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
const timelineData = rootState.statuses.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
- 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 })
@@ -38,7 +38,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
diff --git a/src/sw.js b/src/sw.js
index 9118dd71..70fed44b 100644
--- a/src/sw.js
+++ b/src/sw.js
@@ -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() }
}