Fix merge conflicts

This commit is contained in:
Sean King 2022-08-06 22:02:21 -06:00
commit dc9951d400
No known key found for this signature in database
GPG Key ID: 510C52BACD6E7257
166 changed files with 2374 additions and 1602 deletions

View File

@ -1,7 +1,7 @@
module.exports = { module.exports = {
root: true, root: true,
parserOptions: { parserOptions: {
parser: 'babel-eslint', parser: '@babel/eslint-parser',
sourceType: 'module' sourceType: 'module'
}, },
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
@ -21,6 +21,7 @@ module.exports = {
'generator-star-spacing': 0, 'generator-star-spacing': 0,
// allow debugger during development // allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'vue/require-prop-types': 0 'vue/require-prop-types': 0,
'vue/multi-word-component-names': 0
} }
} }

View File

@ -1,7 +1,7 @@
# This file is a template, and might need editing before it works on your project. # This file is a template, and might need editing before it works on your project.
# Official framework image. Look for the different tagged releases at: # Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/node/tags/ # https://hub.docker.com/r/library/node/tags/
image: node:12 image: node:16
stages: stages:
- lint - lint

View File

@ -1 +1 @@
7.2.1 16.16.0

View File

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

View File

@ -5,6 +5,8 @@ var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin') var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
var CopyPlugin = require('copy-webpack-plugin'); var CopyPlugin = require('copy-webpack-plugin');
var { VueLoaderPlugin } = require('vue-loader') var { VueLoaderPlugin } = require('vue-loader')
var ESLintPlugin = require('eslint-webpack-plugin');
var env = process.env.NODE_ENV var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the // check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@ -30,7 +32,7 @@ module.exports = {
} }
}, },
resolve: { resolve: {
extensions: ['.js', '.jsx', '.vue'], extensions: ['.mjs', '.js', '.jsx', '.vue'],
modules: [ modules: [
path.join(__dirname, '../node_modules') path.join(__dirname, '../node_modules')
], ],
@ -45,20 +47,6 @@ module.exports = {
module: { module: {
noParse: /node_modules\/localforage\/dist\/localforage.js/, noParse: /node_modules\/localforage\/dist\/localforage.js/,
rules: [ rules: [
{
enforce: 'pre',
test: /\.(js|vue)$/,
include: projectRoot,
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter'),
sourceMap: config.build.productionSourceMap,
extract: true
}
}
},
{ {
enforce: 'post', enforce: 'post',
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
@ -108,6 +96,11 @@ module.exports = {
} }
} }
}, },
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto'
}
] ]
}, },
plugins: [ plugins: [
@ -115,12 +108,16 @@ module.exports = {
entry: path.join(__dirname, '..', 'src/sw.js'), entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js' filename: 'sw-pleroma.js'
}), }),
new ESLintPlugin({
extensions: ['js', 'vue'],
formatter: require('eslint-formatter-friendly')
}),
new VueLoaderPlugin(), new VueLoaderPlugin(),
// This copies Ruffle's WASM to a directory so that JS side can access it // This copies Ruffle's WASM to a directory so that JS side can access it
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ {
from: "node_modules/ruffle-mirror/*", from: "node_modules/@ruffle-rs/ruffle/*",
to: "static/ruffle", to: "static/ruffle",
flatten: true flatten: true
}, },

View File

@ -16,16 +16,17 @@
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.18.6", "@babel/runtime": "7.18.9",
"@chenfengyuan/vue-qrcode": "2.0.0", "@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "1.3.0", "@fortawesome/fontawesome-svg-core": "6.1.2",
"@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-regular-svg-icons": "6.1.2",
"@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "6.1.2",
"@fortawesome/vue-fontawesome": "3.0.1", "@fortawesome/vue-fontawesome": "3.0.1",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0", "@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@vuelidate/core": "2.0.0-alpha.42", "@ruffle-rs/ruffle": "^0.1.0-nightly.2022.7.12",
"@vuelidate/validators": "2.0.0-alpha.30", "@vuelidate/core": "2.0.0-alpha.43",
"body-scroll-lock": "2.7.1", "@vuelidate/validators": "2.0.0-alpha.31",
"body-scroll-lock": "3.1.5",
"chromatism": "3.0.0", "chromatism": "3.0.0",
"click-outside-vue3": "4.0.1", "click-outside-vue3": "4.0.1",
"cropperjs": "1.5.12", "cropperjs": "1.5.12",
@ -37,46 +38,44 @@
"phoenix": "1.6.2", "phoenix": "1.6.2",
"punycode.js": "2.1.0", "punycode.js": "2.1.0",
"qrcode": "1", "qrcode": "1",
"ruffle-mirror": "2021.12.31",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"vue": "^3.2.31", "vue": "3.2.37",
"vue-i18n": "^9.2.0-beta.34", "vue-i18n": "9.2.0",
"vue-router": "4.0.14", "vue-router": "4.1.3",
"vue-template-compiler": "2.6.11", "vue-template-compiler": "2.7.8",
"vuex": "4.0.2" "vuex": "4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.18.6", "@babel/core": "7.18.9",
"@babel/plugin-transform-runtime": "7.18.6", "@babel/plugin-transform-runtime": "7.18.9",
"@babel/preset-env": "7.18.6", "@babel/preset-env": "7.18.9",
"@babel/register": "7.18.6", "@babel/register": "7.18.9",
"@babel/eslint-parser": "7.18.9",
"@intlify/vue-i18n-loader": "^5.0.0", "@intlify/vue-i18n-loader": "^5.0.0",
"@ungap/event-target": "0.2.3", "@ungap/event-target": "0.2.3",
"@vue/babel-helper-vue-jsx-merge-props": "1.2.1", "@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
"@vue/babel-plugin-jsx": "1.1.1", "@vue/babel-plugin-jsx": "1.1.1",
"@vue/compiler-sfc": "^3.1.0", "@vue/compiler-sfc": "3.2.37",
"@vue/test-utils": "2.0.0-rc.17", "@vue/test-utils": "2.0.2",
"autoprefixer": "6.7.7", "autoprefixer": "6.7.7",
"babel-eslint": "7.2.3",
"babel-loader": "8.2.5", "babel-loader": "8.2.5",
"babel-plugin-lodash": "3.3.4", "babel-plugin-lodash": "3.3.4",
"chai": "3.5.0", "chai": "3.5.0",
"chalk": "1.1.3", "chalk": "1.1.3",
"chromedriver": "87.0.7", "chromedriver": "103.0.0",
"connect-history-api-fallback": "1.6.0", "connect-history-api-fallback": "1.6.0",
"copy-webpack-plugin": "6.4.1", "copy-webpack-plugin": "6.4.1",
"cross-spawn": "4.0.2", "cross-spawn": "4.0.2",
"css-loader": "0.28.11", "css-loader": "0.28.11",
"custom-event-polyfill": "1.0.7", "custom-event-polyfill": "1.0.7",
"eslint": "5.16.0", "eslint": "8.20.0",
"eslint-config-standard": "12.0.0", "eslint-config-standard": "17.0.0",
"eslint-friendly-formatter": "2.0.7", "eslint-formatter-friendly": "7.0.0",
"eslint-loader": "2.2.1", "eslint-webpack-plugin": "2.7.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-node": "7.0.1", "eslint-plugin-n": "15.2.4",
"eslint-plugin-promise": "4.3.1", "eslint-plugin-promise": "6.0.0",
"eslint-plugin-standard": "4.1.0", "eslint-plugin-vue": "9.3.0",
"eslint-plugin-vue": "5.2.3",
"eventsource-polyfill": "0.9.6", "eventsource-polyfill": "0.9.6",
"express": "4.18.1", "express": "4.18.1",
"file-loader": "3.0.1", "file-loader": "3.0.1",
@ -105,7 +104,7 @@
"ora": "0.4.1", "ora": "0.4.1",
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"sass": "1.53.0", "sass": "1.54.0",
"sass-loader": "7.3.1", "sass-loader": "7.3.1",
"selenium-server": "2.53.1", "selenium-server": "2.53.1",
"semver": "5.7.1", "semver": "5.7.1",
@ -118,7 +117,7 @@
"stylelint-rscss": "0.4.0", "stylelint-rscss": "0.4.0",
"url-loader": "1.1.2", "url-loader": "1.1.2",
"vue-loader": "^16.0.0", "vue-loader": "^16.0.0",
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.3",
"webpack": "4.46.0", "webpack": "4.46.0",
"webpack-dev-middleware": "3.7.3", "webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "2.25.1", "webpack-hot-middleware": "2.25.1",

View File

@ -4,7 +4,6 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance
import FeaturesPanel from './components/features_panel/features_panel.vue' import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_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 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 MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue' import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
@ -32,7 +31,7 @@ export default {
MobilePostStatusButton, MobilePostStatusButton,
MobileNav, MobileNav,
DesktopNav, DesktopNav,
SettingsModal, SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
UserReportingModal, UserReportingModal,
PostStatusModal, PostStatusModal,
GlobalNoticeList GlobalNoticeList

View File

@ -4,6 +4,13 @@
:root { :root {
--navbar-height: 3.5rem; --navbar-height: 3.5rem;
--post-line-height: 1.4; --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 { html {
@ -117,7 +124,7 @@ i[class*=icon-],
} }
nav { nav {
z-index: 1000; z-index: var(--ZI_navbar);
color: var(--topBarText); color: var(--topBarText);
background-color: $fallback--fg; background-color: $fallback--fg;
background-color: var(--topBar, $fallback--fg); background-color: var(--topBar, $fallback--fg);
@ -828,7 +835,7 @@ option {
// Vue transitions // Vue transitions
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.2s; transition: opacity 0.3s;
} }
.fade-enter-from, .fade-enter-from,

View File

@ -15,8 +15,12 @@
class="app-layout container" class="app-layout container"
:class="classes" :class="classes"
> >
<div class="underlay"/> <div class="underlay" />
<div id="sidebar" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"> <div
id="sidebar"
class="column -scrollable"
:class="{ '-show-scrollbar': showScrollbars }"
>
<user-panel /> <user-panel />
<template v-if="layoutType !== 'mobile'"> <template v-if="layoutType !== 'mobile'">
<nav-panel /> <nav-panel />
@ -26,7 +30,11 @@
<div id="notifs-sidebar" /> <div id="notifs-sidebar" />
</template> </template>
</div> </div>
<div id="main-scroller" class="column main" :class="{ '-full-height': isChats }"> <div
id="main-scroller"
class="column main"
:class="{ '-full-height': isChats }"
>
<div <div
v-if="!currentUser" v-if="!currentUser"
class="login-hint panel panel-default" class="login-hint panel panel-default"
@ -40,9 +48,13 @@
</div> </div>
<router-view /> <router-view />
</div> </div>
<div id="notifs-column" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"/> <div
id="notifs-column"
class="column -scrollable"
:class="{ '-show-scrollbar': showScrollbars }"
/>
</div> </div>
<media-modal /> <MediaModal />
<shout-panel <shout-panel
v-if="currentUser && shout && !hideShoutbox" v-if="currentUser && shout && !hideShoutbox"
:floating="true" :floating="true"
@ -55,6 +67,7 @@
<SettingsModal /> <SettingsModal />
<div id="modal" /> <div id="modal" />
<GlobalNoticeList /> <GlobalNoticeList />
<div id="popovers" />
</div> </div>
</template> </template>

View File

@ -156,7 +156,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('hideSitename') copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight') copyInstanceOption('sidebarRight')
return store.dispatch('setTheme', config['theme']) return store.dispatch('setTheme', config.theme)
} }
const getTOS = async ({ store }) => { const getTOS = async ({ store }) => {
@ -197,7 +197,7 @@ const getStickers = async ({ store }) => {
const stickers = (await Promise.all( const stickers = (await Promise.all(
Object.entries(values).map(async ([name, path]) => { Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json') const resPack = await window.fetch(path + 'pack.json')
var meta = {} let meta = {}
if (resPack.ok) { if (resPack.ok) {
meta = await resPack.json() meta = await resPack.json()
} }
@ -319,6 +319,7 @@ const setConfig = async ({ store }) => {
} }
const checkOAuthToken = async ({ store }) => { const checkOAuthToken = async ({ store }) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (store.getters.getUserToken()) { if (store.getters.getUserToken()) {
try { try {
@ -396,6 +397,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
app.component('FAIcon', FontAwesomeIcon) app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers) app.component('FALayers', FontAwesomeLayers)
// remove after vue 3.3
app.config.unwrapInjectedRef = true
app.mount('#app') app.mount('#app')
return app return app

View File

@ -31,7 +31,8 @@ export default (store) => {
} }
let routes = [ let routes = [
{ name: 'root', {
name: 'root',
path: '/', path: '/',
redirect: _to => { redirect: _to => {
return (store.state.users.currentUser return (store.state.users.currentUser
@ -45,12 +46,14 @@ export default (store) => {
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { 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([^/@]+)', path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
component: RemoteUserResolver, component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute beforeEnter: validateAuthenticatedRoute
}, },
{ name: 'remote-user-profile', {
name: 'remote-user-profile',
path: '/remote-users/:hostname/:username', path: '/remote-users/:hostname/:username',
component: RemoteUserResolver, component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute beforeEnter: validateAuthenticatedRoute

View File

@ -8,7 +8,7 @@
</div> </div>
</template> </template>
<script src="./about.js" ></script> <script src="./about.js"></script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@ -6,7 +6,7 @@
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
remove-padding remove-padding
> >
<template v-slot:content> <template #content>
<div class="dropdown-menu"> <div class="dropdown-menu">
<template v-if="relationship.following"> <template v-if="relationship.following">
<button <button
@ -57,7 +57,7 @@
</button> </button>
</div> </div>
</template> </template>
<template v-slot:trigger> <template #trigger>
<button class="button-unstyled ellipsis-button"> <button class="button-unstyled ellipsis-button">
<FAIcon <FAIcon
class="icon" class="icon"

View File

@ -14,7 +14,7 @@
</div> </div>
</template> </template>
<script src="./avatar_list.js" ></script> <script src="./avatar_list.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

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

View File

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

View File

@ -107,7 +107,7 @@ const Chat = {
} }
}) })
}, },
'$route': function () { $route: function () {
this.startFetching() this.startFetching()
}, },
mastoUserSocketStatus (newValue) { mastoUserSocketStatus (newValue) {

View File

@ -23,7 +23,7 @@
class="timeline" class="timeline"
> >
<List :items="sortedChatList"> <List :items="sortedChatList">
<template v-slot:item="{item}"> <template #item="{item}">
<ChatListItem <ChatListItem
:key="item.id" :key="item.id"
:compact="false" :compact="false"

View File

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

View File

@ -14,16 +14,16 @@
v-if="!isCurrentUser" v-if="!isCurrentUser"
class="avatar-wrapper" class="avatar-wrapper"
> >
<router-link <UserPopover
v-if="chatViewItem.isHead" v-if="chatViewItem.isHead"
:to="userProfileLink" :user-id="author.id"
> >
<UserAvatar <UserAvatar
:compact="true" :compact="true"
:better-shadow="betterShadow" :better-shadow="betterShadow"
:user="author" :user="author"
/> />
</router-link> </UserPopover>
</div> </div>
<div class="chat-message-inner"> <div class="chat-message-inner">
<div <div
@ -44,13 +44,13 @@
<Popover <Popover
trigger="click" trigger="click"
placement="top" placement="top"
:bound-to-selector="isCurrentUser ? '' : '.scrollable-message-list'" bound-to-selector=".chat-view-inner"
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
:margin="popoverMarginStyle" :margin="popoverMarginStyle"
@show="menuOpened = true" @show="menuOpened = true"
@close="menuOpened = false" @close="menuOpened = false"
> >
<template v-slot:content> <template #content>
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
class="button-default dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@ -60,7 +60,7 @@
</button> </button>
</div> </div>
</template> </template>
<template v-slot:trigger> <template #trigger>
<button <button
class="button-default menu-icon" class="button-default menu-icon"
:title="$t('chats.more')" :title="$t('chats.more')"
@ -75,7 +75,7 @@
:status="messageForStatusContent" :status="messageForStatusContent"
:full-content="true" :full-content="true"
> >
<template v-slot:footer> <template #footer>
<span <span
class="created-at" class="created-at"
> >
@ -96,7 +96,7 @@
</div> </div>
</template> </template>
<script src="./chat_message.js" ></script> <script src="./chat_message.js"></script>
<style lang="scss"> <style lang="scss">
@import './chat_message.scss'; @import './chat_message.scss';

View File

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

View File

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

View File

@ -22,12 +22,12 @@
<script> <script>
export default { export default {
emits: ['update:modelValue'],
props: [ props: [
'modelValue', 'modelValue',
'indeterminate', 'indeterminate',
'disabled' 'disabled'
] ],
emits: ['update:modelValue']
} }
</script> </script>

View File

@ -46,7 +46,6 @@
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" src="./color_input.scss"></style>
<script> <script>
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js' import { hex2rgb } from '../../services/color_convert/color_convert.js'
@ -108,6 +107,7 @@ export default {
} }
} }
</script> </script>
<style lang="scss" src="./color_input.scss"></style>
<style lang="scss"> <style lang="scss">
.color-control { .color-control {

View File

@ -271,7 +271,7 @@ const conversation = {
result[irid] = result[irid] || [] result[irid] = result[irid] || []
result[irid].push({ result[irid].push({
name: `#${i}`, name: `#${i}`,
id: id id
}) })
} }
i++ i++

View File

@ -31,8 +31,8 @@
keypath="status.show_all_conversation_with_icon" keypath="status.show_all_conversation_with_icon"
tag="button" tag="button"
class="button-unstyled -link" class="button-unstyled -link"
@click.prevent="diveToTopLevel"
scope="global" scope="global"
@click.prevent="diveToTopLevel"
> >
<template #icon> <template #icon>
<FAIcon <FAIcon

View File

@ -46,23 +46,27 @@ export default {
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
logoStyle () { logoStyle () {
return { return {
'visibility': this.enableMask ? 'hidden' : 'visible' visibility: this.enableMask ? 'hidden' : 'visible'
} }
}, },
logoMaskStyle () { logoMaskStyle () {
return this.enableMask ? { return this.enableMask
'mask-image': `url(${this.$store.state.instance.logo})` ? {
} : { 'mask-image': `url(${this.$store.state.instance.logo})`
'background-color': this.enableMask ? '' : 'transparent' }
} : {
'background-color': this.enableMask ? '' : 'transparent'
}
}, },
logoBgStyle () { logoBgStyle () {
return Object.assign({ return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`, margin: `${this.$store.state.instance.logoMargin} 0`,
opacity: this.searchBarHidden ? 1 : 0 opacity: this.searchBarHidden ? 1 : 0
}, this.enableMask ? {} : { }, this.enableMask
'background-color': this.enableMask ? '' : 'transparent' ? {}
}) : {
'background-color': this.enableMask ? '' : 'transparent'
})
}, },
logo () { return this.$store.state.instance.logo }, logo () { return this.$store.state.instance.logo },
sitename () { return this.$store.state.instance.name }, sitename () { return this.$store.state.instance.name },

View File

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

View File

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

View File

@ -9,7 +9,7 @@
class="btn button-default" class="btn button-default"
> >
{{ $t('domain_mute_card.unmute') }} {{ $t('domain_mute_card.unmute') }}
<template v-slot:progress> <template #progress>
{{ $t('domain_mute_card.unmute_progress') }} {{ $t('domain_mute_card.unmute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
@ -19,7 +19,7 @@
class="btn button-default" class="btn button-default"
> >
{{ $t('domain_mute_card.mute') }} {{ $t('domain_mute_card.mute') }}
<template v-slot:progress> <template #progress>
{{ $t('domain_mute_card.mute_progress') }} {{ $t('domain_mute_card.mute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>

View File

@ -321,7 +321,7 @@ const EmojiInput = {
} }
}, },
scrollIntoView () { scrollIntoView () {
const rootRef = this.$refs['picker'].$el const rootRef = this.$refs.picker.$el
/* Scroller is either `window` (replies in TL), sidebar (main post form, /* Scroller is either `window` (replies in TL), sidebar (main post form,
* replies in notifs) or mobile post form. Note that getting and setting * replies in notifs) or mobile post form. Note that getting and setting
* scroll is different for `Window` and `Element`s * scroll is different for `Window` and `Element`s

View File

@ -25,7 +25,7 @@ const filterByKeyword = (list, keyword = '') => {
if (keyword === '') return list if (keyword === '') return list
const keywordLowercase = keyword.toLowerCase() const keywordLowercase = keyword.toLowerCase()
let orderedEmojiList = [] const orderedEmojiList = []
for (const emoji of list) { for (const emoji of list) {
const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase) const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase)
if (indexOfKeyword > -1) { if (indexOfKeyword > -1) {

View File

@ -7,7 +7,8 @@
right: 0; right: 0;
left: 0; left: 0;
margin: 0 !important; margin: 0 !important;
z-index: 100; // TODO: actually use popover in emoji picker
z-index: var(--ZI_popovers);
background-color: $fallback--bg; background-color: $fallback--bg;
background-color: var(--popover, $fallback--bg); background-color: var(--popover, $fallback--bg);
color: $fallback--link; color: $fallback--link;

View File

@ -26,7 +26,7 @@
</div> </div>
</template> </template>
<script src="./emoji_reactions.js" ></script> <script src="./emoji_reactions.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

@ -25,7 +25,7 @@ library.add(
) )
const ExtraButtons = { const ExtraButtons = {
props: [ 'status' ], props: ['status'],
components: { Popover }, components: { Popover },
methods: { methods: {
deleteStatus () { deleteStatus () {
@ -89,6 +89,9 @@ const ExtraButtons = {
canMute () { canMute () {
return !!this.currentUser return !!this.currentUser
}, },
canBookmark () {
return !!this.currentUser
},
statusLink () { statusLink () {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}` return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
} }

View File

@ -7,7 +7,7 @@
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
remove-padding remove-padding
> >
<template v-slot:content="{close}"> <template #content="{close}">
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
v-if="canMute && !status.thread_muted" v-if="canMute && !status.thread_muted"
@ -51,28 +51,30 @@
icon="thumbtack" icon="thumbtack"
/><span>{{ $t("status.unpin") }}</span> /><span>{{ $t("status.unpin") }}</span>
</button> </button>
<button <template v-if="canBookmark">
v-if="!status.bookmarked" <button
class="button-default dropdown-item dropdown-item-icon" v-if="!status.bookmarked"
@click.prevent="bookmarkStatus" class="button-default dropdown-item dropdown-item-icon"
@click="close" @click.prevent="bookmarkStatus"
> @click="close"
<FAIcon >
fixed-width <FAIcon
:icon="['far', 'bookmark']" fixed-width
/><span>{{ $t("status.bookmark") }}</span> :icon="['far', 'bookmark']"
</button> /><span>{{ $t("status.bookmark") }}</span>
<button </button>
v-if="status.bookmarked" <button
class="button-default dropdown-item dropdown-item-icon" v-if="status.bookmarked"
@click.prevent="unbookmarkStatus" class="button-default dropdown-item dropdown-item-icon"
@click="close" @click.prevent="unbookmarkStatus"
> @click="close"
<FAIcon >
fixed-width <FAIcon
icon="bookmark" fixed-width
/><span>{{ $t("status.unbookmark") }}</span> icon="bookmark"
</button> /><span>{{ $t("status.unbookmark") }}</span>
</button>
</template>
<button <button
v-if="canDelete" v-if="canDelete"
class="button-default dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"
@ -118,18 +120,18 @@
</button> </button>
</div> </div>
</template> </template>
<template v-slot:trigger> <template #trigger>
<button class="button-unstyled popover-trigger"> <span class="button-unstyled popover-trigger">
<FAIcon <FAIcon
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="ellipsis-h" icon="ellipsis-h"
/> />
</button> </span>
</template> </template>
</Popover> </Popover>
</template> </template>
<script src="./extra_buttons.js" ></script> <script src="./extra_buttons.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

@ -29,7 +29,7 @@
</div> </div>
</template> </template>
<script src="./favorite_button.js" ></script> <script src="./favorite_button.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

@ -32,7 +32,7 @@
</div> </div>
</template> </template>
<script src="./features_panel.js" ></script> <script src="./features_panel.js"></script>
<style lang="scss"> <style lang="scss">
.features-panel li { .features-panel li {

View File

@ -11,7 +11,7 @@ library.add(
) )
const Flash = { const Flash = {
props: [ 'src' ], props: ['src'],
data () { data () {
return { return {
player: false, // can be true, "hidden", false. hidden = element exists player: false, // can be true, "hidden", false. hidden = element exists

View File

@ -47,7 +47,7 @@
</div> </div>
</template> </template>
<script src="./font_control.js" ></script> <script src="./font_control.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

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

View File

@ -14,6 +14,6 @@
</span> </span>
</template> </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" />

View File

@ -95,7 +95,7 @@ const ImageCropper = {
const fileInput = this.$refs.input const fileInput = this.$refs.input
if (fileInput.files != null && fileInput.files[0] != null) { if (fileInput.files != null && fileInput.files[0] != null) {
this.file = fileInput.files[0] this.file = fileInput.files[0]
let reader = new window.FileReader() const reader = new window.FileReader()
reader.onload = (e) => { reader.onload = (e) => {
this.dataUrl = e.target.result this.dataUrl = e.target.result
this.$emit('open') this.$emit('open')

View File

@ -10,4 +10,4 @@
</div> </div>
</template> </template>
<script src="./instance_specific_panel.js" ></script> <script src="./instance_specific_panel.js"></script>

View File

@ -14,7 +14,7 @@ const Interactions = {
data () { data () {
return { return {
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
filterMode: tabModeDict['mentions'], filterMode: tabModeDict.mentions,
canSeeReports: ['moderator', 'admin'].includes(this.$store.state.users.currentUser.role) canSeeReports: ['moderator', 'admin'].includes(this.$store.state.users.currentUser.role)
} }
}, },

View File

@ -25,6 +25,7 @@ import Select from '../select/select.vue'
export default { export default {
components: { components: {
// eslint-disable-next-line vue/no-reserved-component-names
Select Select
}, },
props: { props: {

View File

@ -83,7 +83,7 @@ const LoginForm = {
}, },
clearError () { this.error = false }, clearError () { this.error = false },
focusOnPasswordInput () { focusOnPasswordInput () {
let passwordInput = this.$refs.passwordInput const passwordInput = this.$refs.passwordInput
passwordInput.focus() passwordInput.focus()
passwordInput.setSelectionRange(0, passwordInput.value.length) passwordInput.setSelectionRange(0, passwordInput.value.length)
} }

View File

@ -90,7 +90,7 @@
</div> </div>
</template> </template>
<script src="./login_form.js" ></script> <script src="./login_form.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

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

View File

@ -42,7 +42,8 @@ const mediaUpload = {
.then((fileData) => { .then((fileData) => {
self.$emit('uploaded', fileData) self.$emit('uploaded', fileData)
self.decreaseUploadCount() self.decreaseUploadCount()
}, (error) => { // eslint-disable-line handle-callback-err }, (error) => {
console.error('Error uploading file', error)
self.$emit('upload-failed', 'default') self.$emit('upload-failed', 'default')
self.decreaseUploadCount() self.decreaseUploadCount()
}) })
@ -73,7 +74,7 @@ const mediaUpload = {
'disabled' 'disabled'
], ],
watch: { watch: {
'dropFiles': function (fileInfos) { dropFiles: function (fileInfos) {
if (!this.uploading) { if (!this.uploading) {
this.multiUpload(fileInfos) this.multiUpload(fileInfos)
} }

View File

@ -26,7 +26,7 @@
</label> </label>
</template> </template>
<script src="./media_upload.js" ></script> <script src="./media_upload.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

@ -2,6 +2,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faAt faAt
@ -14,7 +15,8 @@ library.add(
const MentionLink = { const MentionLink = {
name: 'MentionLink', name: 'MentionLink',
components: { components: {
UserAvatar UserAvatar,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
}, },
props: { props: {
url: { url: {
@ -34,15 +36,30 @@ const MentionLink = {
type: String type: String
} }
}, },
data () {
return {
hasSelection: false
}
},
methods: { methods: {
onClick () { onClick () {
if (this.shouldShowTooltip) return
const link = generateProfileLink( const link = generateProfileLink(
this.userId || this.user.id, this.userId || this.user.id,
this.userScreenName || this.user.screen_name this.userScreenName || this.user.screen_name
) )
this.$router.push(link) 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: { computed: {
user () { user () {
return this.url && this.$store && this.$store.getters.findUserByUrl(this.url) return this.url && this.$store && this.$store.getters.findUserByUrl(this.url)
@ -88,7 +105,8 @@ const MentionLink = {
return [ return [
{ {
'-you': this.isYou && this.shouldBoldenYou, '-you': this.isYou && this.shouldBoldenYou,
'-highlighted': this.highlight '-highlighted': this.highlight,
'-has-selection': this.hasSelection
}, },
this.highlightType this.highlightType
] ]
@ -110,7 +128,7 @@ const MentionLink = {
} }
}, },
shouldShowTooltip () { shouldShowTooltip () {
return this.mergedConfig.mentionLinkShowTooltip && this.mergedConfig.mentionLinkDisplay === 'short' && this.isRemote return this.mergedConfig.mentionLinkShowTooltip
}, },
shouldShowAvatar () { shouldShowAvatar () {
return this.mergedConfig.mentionLinkShowAvatar return this.mergedConfig.mentionLinkShowAvatar

View File

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

View File

@ -9,69 +9,64 @@
class="original" class="original"
target="_blank" target="_blank"
v-html="content" v-html="content"
/><!-- eslint-enable vue/no-v-html --><span /><!-- eslint-enable vue/no-v-html -->
v-if="user" <UserPopover
class="new" v-else
:style="style" :user-id="user.id"
:class="classnames" :disabled="!shouldShowTooltip"
> >
<a <span
class="short button-unstyled" v-if="user"
:class="{ '-with-tooltip': shouldShowTooltip }" class="new"
:href="url" :style="style"
@click.prevent="onClick" :class="classnames"
> >
<!-- eslint-disable vue/no-v-html --> <a
<UserAvatar class="short button-unstyled"
v-if="shouldShowAvatar" :class="{ '-with-tooltip': shouldShowTooltip }"
class="mention-avatar" :href="url"
:user="user" @click.prevent="onClick"
/><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"
> >
<!-- eslint-disable vue/no-v-html --> <!-- 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" class="userName"
v-html="userName" v-html="userName"
/><span /><span
v-if="shouldShowFullUserName"
class="serverName" class="serverName"
:class="{ '-faded': shouldFadeDomain }" :class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName" 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 --> <!-- eslint-enable vue/no-v-html -->
</span> </span>
</span> </span>
</span> </UserPopover>
</span> </span>
</template> </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" />

View File

@ -13,14 +13,13 @@
<span <span
v-if="expanded" v-if="expanded"
class="fullExtraMentions" class="fullExtraMentions"
> >{{ ' ' }}<MentionLink
<MentionLink v-for="mention in extraMentions"
v-for="mention in extraMentions" :key="mention.index"
:key="mention.index" class="mention-link"
class="mention-link" :content="mention.content"
:content="mention.content" :url="mention.url"
:url="mention.url" />
/>
</span><button </span><button
v-if="!expanded" v-if="!expanded"
class="button-unstyled showMoreLess" class="button-unstyled showMoreLess"
@ -37,5 +36,5 @@
</span> </span>
</span> </span>
</template> </template>
<script src="./mentions_line.js" ></script> <script src="./mentions_line.js"></script>
<style lang="scss" src="./mentions_line.scss" /> <style lang="scss" src="./mentions_line.scss" />

View File

@ -69,4 +69,4 @@
</div> </div>
</div> </div>
</template> </template>
<script src="./recovery_form.js" ></script> <script src="./recovery_form.js"></script>

View File

@ -67,11 +67,10 @@
</a> </a>
</div> </div>
<div <div
class="mobile-notifications"
id="mobile-notifications" id="mobile-notifications"
class="mobile-notifications"
@scroll="onScroll" @scroll="onScroll"
> />
</div>
</div> </div>
<SideDrawer <SideDrawer
ref="sideDrawer" ref="sideDrawer"
@ -86,6 +85,8 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.MobileNav { .MobileNav {
z-index: var(--ZI_navbar);
.mobile-nav { .mobile-nav {
display: grid; display: grid;
line-height: var(--navbar-height); line-height: var(--navbar-height);
@ -147,7 +148,7 @@
transition-property: transform; transition-property: transform;
transition-duration: 0.25s; transition-duration: 0.25s;
transform: translateX(0); transform: translateX(0);
z-index: 1001; z-index: var(--ZI_navbar);
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
&.-closed { &.-closed {
@ -160,7 +161,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
z-index: 1; z-index: calc(var(--ZI_navbar) + 100);
width: 100%; width: 100%;
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;

View File

@ -12,6 +12,9 @@
<script> <script>
export default { export default {
provide: {
popoversZLayer: 'modals'
},
props: { props: {
isOpen: { isOpen: {
type: Boolean, type: Boolean,
@ -26,7 +29,7 @@ export default {
classes () { classes () {
return { return {
'modal-background': !this.noBackground, 'modal-background': !this.noBackground,
'open': this.isOpen open: this.isOpen
} }
} }
} }
@ -35,7 +38,7 @@ export default {
<style lang="scss"> <style lang="scss">
.modal-view { .modal-view {
z-index: 2000; z-index: var(--ZI_modals);
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;

View File

@ -8,7 +8,7 @@
@show="setToggled(true)" @show="setToggled(true)"
@close="setToggled(false)" @close="setToggled(false)"
> >
<template v-slot:content> <template #content>
<div class="dropdown-menu"> <div class="dropdown-menu">
<span v-if="user.is_local"> <span v-if="user.is_local">
<button <button
@ -122,7 +122,7 @@
</span> </span>
</div> </div>
</template> </template>
<template v-slot:trigger> <template #trigger>
<button <button
class="btn button-default btn-block moderation-tools-button" class="btn button-default btn-block moderation-tools-button"
:class="{ toggled }" :class="{ toggled }"
@ -137,11 +137,11 @@
v-if="showDeleteUserDialog" v-if="showDeleteUserDialog"
:on-cancel="deleteUserDialog.bind(this, false)" :on-cancel="deleteUserDialog.bind(this, false)"
> >
<template v-slot:header> <template #header>
{{ $t('user_card.admin_menu.delete_user') }} {{ $t('user_card.admin_menu.delete_user') }}
</template> </template>
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p> <p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
<template v-slot:footer> <template #footer>
<button <button
class="btn button-default" class="btn button-default"
@click="deleteUserDialog(false)" @click="deleteUserDialog(false)"

View File

@ -9,10 +9,10 @@ import { get } from 'lodash'
*/ */
const toInstanceReasonObject = (instances, info, key) => { const toInstanceReasonObject = (instances, info, key) => {
return instances.map(instance => { return instances.map(instance => {
if (info[key] && info[key][instance] && info[key][instance]['reason']) { if (info[key] && info[key][instance] && info[key][instance].reason) {
return { instance: instance, reason: info[key][instance]['reason'] } return { instance, reason: info[key][instance].reason }
} }
return { instance: instance, reason: '' } return { instance, reason: '' }
}) })
} }

View File

@ -90,7 +90,7 @@
</div> </div>
</template> </template>
<script src="./nav_panel.js" ></script> <script src="./nav_panel.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
@ -113,7 +113,9 @@
border-color: $fallback--border; border-color: $fallback--border;
border-color: var(--border, $fallback--border); border-color: var(--border, $fallback--border);
padding: 0; padding: 0;
}
> li {
&:first-child .menu-item { &:first-child .menu-item {
border-top-right-radius: $fallback--panelRadius; border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius); border-top-right-radius: var(--panelRadius, $fallback--panelRadius);

View File

@ -6,6 +6,7 @@ import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue' import Timeago from '../timeago/timeago.vue'
import Report from '../report/report.vue' import Report from '../report/report.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx' 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 { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -40,7 +41,7 @@ const Notification = {
unmuted: false unmuted: false
} }
}, },
props: [ 'notification' ], props: ['notification'],
components: { components: {
StatusContent, StatusContent,
UserAvatar, UserAvatar,
@ -48,7 +49,8 @@ const Notification = {
Timeago, Timeago,
Status, Status,
Report, Report,
RichContent RichContent,
UserPopover
}, },
methods: { methods: {
toggleUserExpanded () { toggleUserExpanded () {

View File

@ -34,21 +34,22 @@
<a <a
class="avatar-container" class="avatar-container"
:href="$router.resolve(userProfileLink).href" :href="$router.resolve(userProfileLink).href"
@click.stop.prevent.capture="toggleUserExpanded" @click.prevent
> >
<UserAvatar <UserPopover
:compact="true" :user-id="notification.from_profile.id"
:better-shadow="betterShadow" :overlay-centers="true"
:user="notification.from_profile" >
/> <UserAvatar
class="post-avatar"
:bot="botIndicator"
:compact="true"
:better-shadow="betterShadow"
:user="notification.from_profile"
/>
</UserPopover>
</a> </a>
<div class="notification-right"> <div class="notification-right">
<UserCard
v-if="userExpanded"
:user-id="getUser(notification).id"
:rounded="true"
:bordered="true"
/>
<span class="notification-details"> <span class="notification-details">
<div class="name-and-action"> <div class="name-and-action">
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->

View File

@ -5,7 +5,7 @@
placement="bottom" placement="bottom"
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
> >
<template v-slot:content> <template #content>
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
class="button-default dropdown-item" class="button-default dropdown-item"
@ -72,7 +72,7 @@
</button> </button>
</div> </div>
</template> </template>
<template v-slot:trigger> <template #trigger>
<button class="filter-trigger-button button-unstyled"> <button class="filter-trigger-button button-unstyled">
<FAIcon icon="filter" /> <FAIcon icon="filter" />
</button> </button>

View File

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

View File

@ -1,5 +1,8 @@
<template> <template>
<teleport :disabled="minimalMode || disableTeleport" :to="teleportTarget"> <teleport
:disabled="minimalMode || disableTeleport"
:to="teleportTarget"
>
<div <div
:class="{ minimal: minimalMode }" :class="{ minimal: minimalMode }"
class="Notifications" class="Notifications"

View File

@ -84,7 +84,7 @@
:key="unit" :key="unit"
:value="unit" :value="unit"
> >
{{ $t(`time.${unit}_short`, ['']) }} {{ $tc(`time.unit.${unit}_short`, expiryAmount, ['']) }}
</option> </option>
</Select> </Select>
</div> </div>

View File

@ -31,13 +31,35 @@ const Popover = {
// If true, subtract padding when calculating position for the popover, // If true, subtract padding when calculating position for the popover,
// use it when popover offset looks to be different on top vs bottom. // 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 () { data () {
return { 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, hidden: true,
styles: { opacity: 0 }, styles: {},
oldSize: { width: 0, height: 0 } oldSize: { width: 0, height: 0 },
scrollable: null,
// used to avoid blinking if hovered onto popover
graceTimeout: null,
parentPopover: null,
childrenShown: new Set()
} }
}, },
methods: { methods: {
@ -47,9 +69,7 @@ const Popover = {
}, },
updateStyles () { updateStyles () {
if (this.hidden) { if (this.hidden) {
this.styles = { this.styles = {}
opacity: 0
}
return return
} }
@ -57,14 +77,26 @@ const Popover = {
// its children are what are inside the slot. Expect only one v-slot:trigger. // 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 const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
// SVGs don't have offsetWidth/Height, use fallback // SVGs don't have offsetWidth/Height, use fallback
const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
const screenBox = anchorEl.getBoundingClientRect() const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
// Screen position of the origin point for popover const anchorScreenBox = anchorEl.getBoundingClientRect()
const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
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 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 // 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.boundTo.x === 'container' || this.boundTo.y === 'container') &&
this.containerBoundingClientRect() this.containerBoundingClientRect()
@ -72,82 +104,156 @@ const Popover = {
// What are the screen bounds for the popover? Viewport vs container // What are the screen bounds for the popover? Viewport vs container
// when using viewport, using default margin values to dodge the navbar // when using viewport, using default margin values to dodge the navbar
const xBounds = this.boundTo && this.boundTo.x === 'container' ? { const xBounds = this.boundTo && this.boundTo.x === 'container'
min: parentBounds.left + (margin.left || 0), ? {
max: parentBounds.right - (margin.right || 0) min: parentScreenBox.left + (margin.left || 0),
} : { max: parentScreenBox.right - (margin.right || 0)
min: 0 + (margin.left || 10), }
max: window.innerWidth - (margin.right || 10) : {
} min: 0 + (margin.left || 10),
max: window.innerWidth - (margin.right || 10)
}
const yBounds = this.boundTo && this.boundTo.y === 'container' ? { const yBounds = this.boundTo && this.boundTo.y === 'container'
min: parentBounds.top + (margin.top || 0), ? {
max: parentBounds.bottom - (margin.bottom || 0) min: parentScreenBox.top + (margin.top || 0),
} : { max: parentScreenBox.bottom - (margin.bottom || 0)
min: 0 + (margin.top || 50), }
max: window.innerHeight - (margin.bottom || 5) : {
} min: 0 + (margin.top || 50),
max: window.innerHeight - (margin.bottom || 5)
}
let horizOffset = 0 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 overflowing from left, move it so that it doesn't
if ((origin.x - content.offsetWidth * 0.5) < xBounds.min) { if (leftBorder < xBounds.min) {
horizOffset += -(origin.x - content.offsetWidth * 0.5) + xBounds.min horizOffset += xBounds.min - leftBorder
} }
// If overflowing from right, move it so that it doesn't // If overflowing from right, move it so that it doesn't
if ((origin.x + horizOffset + content.offsetWidth * 0.5) > xBounds.max) { if (rightBorder > xBounds.max) {
horizOffset -= (origin.x + horizOffset + content.offsetWidth * 0.5) - xBounds.max horizOffset -= rightBorder - xBounds.max
} }
// Default to whatever user wished with placement prop // If overflowing from top, move it so that it doesn't
let usingTop = this.placement !== 'bottom' if (topBorder < yBounds.min) {
vertOffset += yBounds.min - topBorder
// 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
let vPadding = 0
if (this.removePadding && usingTop) {
const anchorStyle = getComputedStyle(anchorEl)
vPadding = parseFloat(anchorStyle.paddingTop) + parseFloat(anchorStyle.paddingBottom)
} }
const yOffset = (this.offset && this.offset.y) || 0 // If overflowing from bottom, move it so that it doesn't
const translateY = usingTop if (bottomBorder > yBounds.max) {
? -anchorHeight + vPadding - yOffset - content.offsetHeight vertOffset -= bottomBorder - yBounds.max
: yOffset }
const xOffset = (this.offset && this.offset.x) || 0 let translateX = 0
const translateX = anchorWidth * 0.5 - content.offsetWidth * 0.5 + horizOffset + xOffset 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'
// 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 = { this.styles = {
opacity: 1, left: `${Math.round(translateX)}px`,
transform: `translateX(${Math.round(translateX)}px) translateY(${Math.round(translateY)}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 () { showPopover () {
if (this.disabled) return
const wasHidden = this.hidden const wasHidden = this.hidden
this.hidden = false 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(() => { this.$nextTick(() => {
if (wasHidden) this.$emit('show') if (wasHidden) this.$emit('show')
this.updateStyles() this.updateStyles()
}) })
}, },
hidePopover () { hidePopover () {
if (this.disabled) return
if (!this.hidden) this.$emit('close') if (!this.hidden) this.$emit('close')
this.hidden = true 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) { 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) { 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) { onClick (e) {
if (this.trigger === 'click') { if (this.trigger === 'click') {
@ -160,8 +266,24 @@ const Popover = {
}, },
onClickOutside (e) { onClickOutside (e) {
if (this.hidden) return if (this.hidden) return
if (this.$refs.content && this.$refs.content.contains(e.target)) return
if (this.$el.contains(e.target)) return if (this.$el.contains(e.target)) return
if (this.childrenShown.size > 0) return
this.hidePopover() 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 () { updated () {
@ -175,11 +297,18 @@ const Popover = {
this.oldSize = { width: content.offsetWidth, height: content.offsetHeight } this.oldSize = { width: content.offsetWidth, height: content.offsetHeight }
} }
}, },
created () { mounted () {
document.addEventListener('click', this.onClickOutside) 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 () { beforeUnmount () {
document.removeEventListener('click', this.onClickOutside)
this.hidePopover() this.hidePopover()
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div <span
@mouseenter="onMouseenter" @mouseenter="onMouseenter"
@mouseleave="onMouseleave" @mouseleave="onMouseleave"
> >
@ -11,20 +11,27 @@
> >
<slot name="trigger" /> <slot name="trigger" />
</button> </button>
<div <teleport to="#popovers">
v-if="!hidden" <transition name="fade">
ref="content" <div
:style="styles" v-if="!hidden"
class="popover" ref="content"
:class="popoverClass || 'popover-default'" :style="styles"
> class="popover"
<slot :class="popoverClass || 'popover-default'"
name="content" @mouseenter="onMouseenterContent"
class="popover-inner" @mouseleave="onMouseleaveContent"
:close="hidePopover" @click="onClickContent"
/> >
</div> <slot
</div> name="content"
class="popover-inner"
:close="hidePopover"
/>
</div>
</transition>
</teleport>
</span>
</template> </template>
<script src="./popover.js" /> <script src="./popover.js" />
@ -37,14 +44,15 @@
} }
.popover { .popover {
z-index: 500; z-index: var(--ZI_popover_override, var(--ZI_popovers));
position: absolute; position: fixed;
min-width: 0; 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 { .popover-default {
transition: opacity 0.3s;
&:after { &:after {
content: ''; content: '';
position: absolute; position: absolute;
@ -80,7 +88,7 @@
text-align: left; text-align: left;
list-style: none; list-style: none;
max-width: 100vw; max-width: 100vw;
z-index: 200; z-index: var(--ZI_popover_override, var(--ZI_popovers));
white-space: nowrap; white-space: nowrap;
.dropdown-divider { .dropdown-divider {

View File

@ -41,7 +41,7 @@ const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
allAttentions = uniqBy(allAttentions, 'id') allAttentions = uniqBy(allAttentions, 'id')
allAttentions = reject(allAttentions, { id: currentUser.id }) allAttentions = reject(allAttentions, { id: currentUser.id })
let mentions = map(allAttentions, (attention) => { const mentions = map(allAttentions, (attention) => {
return `@${attention.screen_name}` return `@${attention.screen_name}`
}) })
@ -242,7 +242,7 @@ const PostStatusForm = {
}) })
}, },
watch: { watch: {
'newStatus': { newStatus: {
deep: true, deep: true,
handler () { handler () {
this.statusChanged() this.statusChanged()
@ -273,7 +273,7 @@ const PostStatusForm = {
this.$refs.textarea.focus() this.$refs.textarea.focus()
}) })
} }
let el = this.$el.querySelector('textarea') const el = this.$el.querySelector('textarea')
el.style.height = 'auto' el.style.height = 'auto'
el.style.height = undefined el.style.height = undefined
this.error = null this.error = null
@ -392,7 +392,7 @@ const PostStatusForm = {
this.$emit('resize', { delayed: true }) this.$emit('resize', { delayed: true })
}, },
removeMediaFile (fileInfo) { removeMediaFile (fileInfo) {
let index = this.newStatus.files.indexOf(fileInfo) const index = this.newStatus.files.indexOf(fileInfo)
this.newStatus.files.splice(index, 1) this.newStatus.files.splice(index, 1)
this.$emit('resize') this.$emit('resize')
}, },
@ -462,7 +462,7 @@ const PostStatusForm = {
}, },
onEmojiInputInput (e) { onEmojiInputInput (e) {
this.$nextTick(() => { this.$nextTick(() => {
this.resize(this.$refs['textarea']) this.resize(this.$refs.textarea)
}) })
}, },
resize (e) { resize (e) {
@ -477,8 +477,8 @@ const PostStatusForm = {
return return
} }
const formRef = this.$refs['form'] const formRef = this.$refs.form
const bottomRef = this.$refs['bottom'] const bottomRef = this.$refs.bottom
/* Scroller is either `window` (replies in TL), sidebar (main post form, /* Scroller is either `window` (replies in TL), sidebar (main post form,
* replies in notifs) or mobile post form. Note that getting and setting * replies in notifs) or mobile post form. Note that getting and setting
* scroll is different for `Window` and `Element`s * scroll is different for `Window` and `Element`s
@ -564,7 +564,7 @@ const PostStatusForm = {
this.$refs['emoji-input'].resize() this.$refs['emoji-input'].resize()
}, },
showEmojiPicker () { showEmojiPicker () {
this.$refs['textarea'].focus() this.$refs.textarea.focus()
this.$refs['emoji-input'].triggerShowPicker() this.$refs['emoji-input'].triggerShowPicker()
}, },
clearError () { clearError () {

View File

@ -45,7 +45,7 @@ const ReactButton = {
emojis () { emojis () {
if (this.filterWord !== '') { if (this.filterWord !== '') {
const filterWordLowercase = trim(this.filterWord.toLowerCase()) const filterWordLowercase = trim(this.filterWord.toLowerCase())
let orderedEmojiList = [] const orderedEmojiList = []
for (const emoji of this.$store.state.instance.emoji) { for (const emoji of this.$store.state.instance.emoji) {
if (emoji.replacement === this.filterWord) return [emoji] if (emoji.replacement === this.filterWord) return [emoji]

View File

@ -6,15 +6,16 @@
:offset="{ y: 5 }" :offset="{ y: 5 }"
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
remove-padding remove-padding
popover-class="ReactButton popover-default"
@show="focusInput" @show="focusInput"
> >
<template v-slot:content="{close}"> <template #content="{close}">
<div class="reaction-picker-filter"> <div class="reaction-picker-filter">
<input <input
v-model="filterWord" v-model="filterWord"
@input="$event.target.composing = false"
size="1" size="1"
:placeholder="$t('emoji.search_emoji')" :placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
> >
</div> </div>
<div class="reaction-picker"> <div class="reaction-picker">
@ -40,8 +41,8 @@
<div class="reaction-bottom-fader" /> <div class="reaction-bottom-fader" />
</div> </div>
</template> </template>
<template v-slot:trigger> <template #trigger>
<button <span
class="button-unstyled popover-trigger" class="button-unstyled popover-trigger"
:title="$t('tool_tip.add_reaction')" :title="$t('tool_tip.add_reaction')"
> >
@ -49,12 +50,12 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
:icon="['far', 'smile-beam']" :icon="['far', 'smile-beam']"
/> />
</button> </span>
</template> </template>
</Popover> </Popover>
</template> </template>
<script src="./react_button.js" ></script> <script src="./react_button.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

@ -23,6 +23,7 @@
v-model.trim="v$.user.username.$model" v-model.trim="v$.user.username.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
:aria-required="true"
:placeholder="$t('registration.username_placeholder')" :placeholder="$t('registration.username_placeholder')"
> >
</div> </div>
@ -50,6 +51,7 @@
v-model.trim="v$.user.fullname.$model" v-model.trim="v$.user.fullname.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
:aria-required="true"
:placeholder="$t('registration.fullname_placeholder')" :placeholder="$t('registration.fullname_placeholder')"
> >
</div> </div>
@ -71,13 +73,14 @@
<label <label
class="form--label" class="form--label"
for="email" for="email"
>{{ $t('registration.email') }}</label> >{{ accountActivationRequired ? $t('registration.email') : $t('registration.email_optional') }}</label>
<input <input
id="email" id="email"
v-model="v$.user.email.$model" v-model="v$.user.email.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
type="email" type="email"
:aria-required="accountActivationRequired"
> >
</div> </div>
<div <div
@ -95,7 +98,7 @@
<label <label
class="form--label" class="form--label"
for="bio" for="bio"
>{{ $t('registration.bio') }} ({{ $t('general.optional') }})</label> >{{ $t('registration.bio_optional') }}</label>
<textarea <textarea
id="bio" id="bio"
v-model="user.bio" v-model="user.bio"
@ -119,6 +122,7 @@
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
type="password" type="password"
:aria-required="true"
> >
</div> </div>
<div <div
@ -146,6 +150,7 @@
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
type="password" type="password"
:aria-required="true"
> >
</div> </div>
<div <div

View File

@ -1,5 +1,5 @@
export default { export default {
props: [ 'user' ], props: ['user'],
computed: { computed: {
subscribeUrl () { subscribeUrl () {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef

View File

@ -36,7 +36,7 @@
</div> </div>
</template> </template>
<script src="./retweet_button.js" ></script> <script src="./retweet_button.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

@ -16,7 +16,7 @@ const SearchBar = {
error: false error: false
}), }),
watch: { watch: {
'$route': function (route) { $route: function (route) {
if (route.name === 'search') { if (route.name === 'search') {
this.searchTerm = route.query.query this.searchTerm = route.query.query
} }

View File

@ -24,7 +24,7 @@
:items="items" :items="items"
:get-key="getKey" :get-key="getKey"
> >
<template v-slot:item="{item}"> <template #item="{item}">
<div <div
class="selectable-list-item-inner" class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }" :class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
@ -41,7 +41,7 @@
/> />
</div> </div>
</template> </template>
<template v-slot:empty> <template #empty>
<slot name="empty" /> <slot name="empty" />
</template> </template>
</List> </List>

View File

@ -6,14 +6,14 @@
<Popover <Popover
trigger="hover" trigger="hover"
> >
<template v-slot:trigger> <template #trigger>
&nbsp; &nbsp;
<FAIcon <FAIcon
icon="wrench" icon="wrench"
:aria-label="$t('settings.setting_changed')" :aria-label="$t('settings.setting_changed')"
/> />
</template> </template>
<template v-slot:content> <template #content>
<div class="modified-tooltip"> <div class="modified-tooltip">
{{ $t('settings.setting_changed') }} {{ $t('settings.setting_changed') }}
</div> </div>
@ -41,11 +41,11 @@ export default {
.ModifiedIndicator { .ModifiedIndicator {
display: inline-block; display: inline-block;
position: relative; position: relative;
}
.modified-tooltip { .modified-tooltip {
margin: 0.5em 1em; margin: 0.5em 1em;
min-width: 10em; min-width: 10em;
text-align: center; text-align: center;
}
} }
</style> </style>

View File

@ -6,14 +6,14 @@
<Popover <Popover
trigger="hover" trigger="hover"
> >
<template v-slot:trigger> <template #trigger>
&nbsp; &nbsp;
<FAIcon <FAIcon
icon="server" icon="server"
:aria-label="$t('settings.setting_server_side')" :aria-label="$t('settings.setting_server_side')"
/> />
</template> </template>
<template v-slot:content> <template #content>
<div class="serverside-tooltip"> <div class="serverside-tooltip">
{{ $t('settings.setting_server_side') }} {{ $t('settings.setting_server_side') }}
</div> </div>
@ -41,11 +41,11 @@ export default {
.ServerSideIndicator { .ServerSideIndicator {
display: inline-block; display: inline-block;
position: relative; position: relative;
}
.serverside-tooltip { .serverside-tooltip {
margin: 0.5em 1em; margin: 0.5em 1em;
min-width: 10em; min-width: 10em;
text-align: center; text-align: center;
}
} }
</style> </style>

View File

@ -53,7 +53,7 @@
:bound-to="{ x: 'container' }" :bound-to="{ x: 'container' }"
remove-padding remove-padding
> >
<template v-slot:trigger> <template #trigger>
<button <button
class="btn button-default" class="btn button-default"
:title="$t('general.close')" :title="$t('general.close')"
@ -65,7 +65,7 @@
/> />
</button> </button>
</template> </template>
<template v-slot:content="{close}"> <template #content="{close}">
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
class="button-default dropdown-item dropdown-item-icon" class="button-default dropdown-item dropdown-item-icon"

View File

@ -74,6 +74,22 @@
{{ $t('settings.show_scrollbars') }} {{ $t('settings.show_scrollbars') }}
</BooleanSetting> </BooleanSetting>
</li> </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> <li>
<ChoiceSetting <ChoiceSetting
v-if="user" v-if="user"
@ -261,18 +277,14 @@
{{ $t('settings.mention_link_display') }} {{ $t('settings.mention_link_display') }}
</ChoiceSetting> </ChoiceSetting>
</li> </li>
<ul <li>
class="setting-list suboptions" <BooleanSetting
> path="mentionLinkShowTooltip"
<li v-if="mentionLinkDisplay === 'short'"> expert="1"
<BooleanSetting >
path="mentionLinkShowTooltip" {{ $t('settings.mention_link_use_tooltip') }}
expert="1" </BooleanSetting>
> </li>
{{ $t('settings.mention_link_show_tooltip') }}
</BooleanSetting>
</li>
</ul>
<li> <li>
<BooleanSetting <BooleanSetting
path="useAtIcon" path="useAtIcon"

View File

@ -10,7 +10,7 @@
:query="queryUserIds" :query="queryUserIds"
:placeholder="$t('settings.search_user_to_block')" :placeholder="$t('settings.search_user_to_block')"
> >
<template v-slot="row"> <template #default="row">
<BlockCard <BlockCard
:user-id="row.item" :user-id="row.item"
/> />
@ -21,7 +21,7 @@
:refresh="true" :refresh="true"
:get-key="i => i" :get-key="i => i"
> >
<template v-slot:header="{selected}"> <template #header="{selected}">
<div class="bulk-actions"> <div class="bulk-actions">
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
@ -29,7 +29,7 @@
:click="() => blockUsers(selected)" :click="() => blockUsers(selected)"
> >
{{ $t('user_card.block') }} {{ $t('user_card.block') }}
<template v-slot:progress> <template #progress>
{{ $t('user_card.block_progress') }} {{ $t('user_card.block_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
@ -39,16 +39,16 @@
:click="() => unblockUsers(selected)" :click="() => unblockUsers(selected)"
> >
{{ $t('user_card.unblock') }} {{ $t('user_card.unblock') }}
<template v-slot:progress> <template #progress>
{{ $t('user_card.unblock_progress') }} {{ $t('user_card.unblock_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
</div> </div>
</template> </template>
<template v-slot:item="{item}"> <template #item="{item}">
<BlockCard :user-id="item" /> <BlockCard :user-id="item" />
</template> </template>
<template v-slot:empty> <template #empty>
{{ $t('settings.no_blocks') }} {{ $t('settings.no_blocks') }}
</template> </template>
</BlockList> </BlockList>
@ -63,7 +63,7 @@
:query="queryUserIds" :query="queryUserIds"
:placeholder="$t('settings.search_user_to_mute')" :placeholder="$t('settings.search_user_to_mute')"
> >
<template v-slot="row"> <template #default="row">
<MuteCard <MuteCard
:user-id="row.item" :user-id="row.item"
/> />
@ -74,7 +74,7 @@
:refresh="true" :refresh="true"
:get-key="i => i" :get-key="i => i"
> >
<template v-slot:header="{selected}"> <template #header="{selected}">
<div class="bulk-actions"> <div class="bulk-actions">
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
@ -82,7 +82,7 @@
:click="() => muteUsers(selected)" :click="() => muteUsers(selected)"
> >
{{ $t('user_card.mute') }} {{ $t('user_card.mute') }}
<template v-slot:progress> <template #progress>
{{ $t('user_card.mute_progress') }} {{ $t('user_card.mute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
@ -92,16 +92,16 @@
:click="() => unmuteUsers(selected)" :click="() => unmuteUsers(selected)"
> >
{{ $t('user_card.unmute') }} {{ $t('user_card.unmute') }}
<template v-slot:progress> <template #progress>
{{ $t('user_card.unmute_progress') }} {{ $t('user_card.unmute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
</div> </div>
</template> </template>
<template v-slot:item="{item}"> <template #item="{item}">
<MuteCard :user-id="item" /> <MuteCard :user-id="item" />
</template> </template>
<template v-slot:empty> <template #empty>
{{ $t('settings.no_mutes') }} {{ $t('settings.no_mutes') }}
</template> </template>
</MuteList> </MuteList>
@ -114,7 +114,7 @@
:query="queryKnownDomains" :query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')" :placeholder="$t('settings.type_domains_to_mute')"
> >
<template v-slot="row"> <template #default="row">
<DomainMuteCard <DomainMuteCard
:domain="row.item" :domain="row.item"
/> />
@ -125,7 +125,7 @@
:refresh="true" :refresh="true"
:get-key="i => i" :get-key="i => i"
> >
<template v-slot:header="{selected}"> <template #header="{selected}">
<div class="bulk-actions"> <div class="bulk-actions">
<ProgressButton <ProgressButton
v-if="selected.length > 0" v-if="selected.length > 0"
@ -133,16 +133,16 @@
:click="() => unmuteDomains(selected)" :click="() => unmuteDomains(selected)"
> >
{{ $t('domain_mute_card.unmute') }} {{ $t('domain_mute_card.unmute') }}
<template v-slot:progress> <template #progress>
{{ $t('domain_mute_card.unmute_progress') }} {{ $t('domain_mute_card.unmute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
</div> </div>
</template> </template>
<template v-slot:item="{item}"> <template #item="{item}">
<DomainMuteCard :domain="item" /> <DomainMuteCard :domain="item" />
</template> </template>
<template v-slot:empty> <template #empty>
{{ $t('settings.no_mutes') }} {{ $t('settings.no_mutes') }}
</template> </template>
</DomainMuteList> </DomainMuteList>

View File

@ -71,10 +71,12 @@ const ProfileTab = {
}) })
}, },
emojiSuggestor () { emojiSuggestor () {
return suggestor({ emoji: [ return suggestor({
...this.$store.state.instance.emoji, emoji: [
...this.$store.state.instance.customEmoji ...this.$store.state.instance.emoji,
] }) ...this.$store.state.instance.customEmoji
]
})
}, },
userSuggestor () { userSuggestor () {
return suggestor({ store: this.$store }) return suggestor({ store: this.$store })

View File

@ -117,8 +117,8 @@
<button <button
v-if="!isDefaultAvatar && pickAvatarBtnVisible" v-if="!isDefaultAvatar && pickAvatarBtnVisible"
:title="$t('settings.reset_avatar')" :title="$t('settings.reset_avatar')"
@click="resetAvatar"
class="button-unstyled reset-button" class="button-unstyled reset-button"
@click="resetAvatar"
> >
<FAIcon <FAIcon
icon="times" icon="times"

View File

@ -32,8 +32,8 @@ const Mfa = {
components: { components: {
'recovery-codes': RecoveryCodes, 'recovery-codes': RecoveryCodes,
'totp-item': TOTP, 'totp-item': TOTP,
'qrcode': VueQrcode, qrcode: VueQrcode,
'confirm': Confirm confirm: Confirm
}, },
computed: { computed: {
canSetupOTP () { canSetupOTP () {
@ -139,7 +139,7 @@ const Mfa = {
// fetch settings from server // fetch settings from server
async fetchSettings () { async fetchSettings () {
let result = await this.backendInteractor.settingsMFA() const result = await this.backendInteractor.settingsMFA()
if (result.error) return if (result.error) return
this.settings = result.settings this.settings = result.settings
this.settings.available = true this.settings.available = true

View File

@ -10,7 +10,7 @@ export default {
inProgress: false // progress peform request to disable otp method inProgress: false // progress peform request to disable otp method
}), }),
components: { components: {
'confirm': Confirm confirm: Confirm
}, },
computed: { computed: {
isActivated () { isActivated () {

View File

@ -13,7 +13,7 @@ const SecurityTab = {
deletingAccount: false, deletingAccount: false,
deleteAccountConfirmPasswordInput: '', deleteAccountConfirmPasswordInput: '',
deleteAccountError: false, deleteAccountError: false,
changePasswordInputs: [ '', '', '' ], changePasswordInputs: ['', '', ''],
changedPassword: false, changedPassword: false,
changePasswordError: false, changePasswordError: false,
moveAccountTarget: '', moveAccountTarget: '',

View File

@ -29,7 +29,10 @@
{{ $t('settings.style.preview.content') }} {{ $t('settings.style.preview.content') }}
</h4> </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)"> <code style="font-family: var(--postCodeFont)">
{{ $t('settings.style.preview.mono') }} {{ $t('settings.style.preview.mono') }}
</code> </code>

View File

@ -95,11 +95,11 @@ export default {
...Object.keys(SLOT_INHERITANCE) ...Object.keys(SLOT_INHERITANCE)
.map(key => [key, '']) .map(key => [key, ''])
.reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}), .reduce((acc, [key, val]) => ({ ...acc, [key + 'ColorLocal']: val }), {}),
...Object.keys(OPACITIES) ...Object.keys(OPACITIES)
.map(key => [key, '']) .map(key => [key, ''])
.reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}), .reduce((acc, [key, val]) => ({ ...acc, [key + 'OpacityLocal']: val }), {}),
shadowSelected: undefined, shadowSelected: undefined,
shadowsLocal: {}, shadowsLocal: {},
@ -212,12 +212,12 @@ export default {
currentColors () { currentColors () {
return Object.keys(SLOT_INHERITANCE) return Object.keys(SLOT_INHERITANCE)
.map(key => [key, this[key + 'ColorLocal']]) .map(key => [key, this[key + 'ColorLocal']])
.reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
}, },
currentOpacity () { currentOpacity () {
return Object.keys(OPACITIES) return Object.keys(OPACITIES)
.map(key => [key, this[key + 'OpacityLocal']]) .map(key => [key, this[key + 'OpacityLocal']])
.reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {}) .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
}, },
currentRadii () { currentRadii () {
return { return {

View File

@ -112,9 +112,11 @@ export default {
return hex2rgb(this.selected.color) return hex2rgb(this.selected.color)
}, },
style () { style () {
return this.ready ? { return this.ready
boxShadow: getCssShadow(this.fallback) ? {
} : {} boxShadow: getCssShadow(this.fallback)
}
: {}
} }
} }
} }

View File

@ -215,7 +215,7 @@
</div> </div>
</template> </template>
<script src="./shadow_control.js" ></script> <script src="./shadow_control.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';

View File

@ -11,7 +11,7 @@ library.add(
) )
const shoutPanel = { const shoutPanel = {
props: [ 'floating' ], props: ['floating'],
data () { data () {
return { return {
currentMessage: '', currentMessage: '',

View File

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

View File

@ -32,7 +32,7 @@ library.add(
) )
const SideDrawer = { const SideDrawer = {
props: [ 'logout' ], props: ['logout'],
data: () => ({ data: () => ({
closed: true, closed: true,
closeGesture: undefined closeGesture: undefined

View File

@ -204,14 +204,14 @@
</div> </div>
</template> </template>
<script src="./side_drawer.js" ></script> <script src="./side_drawer.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.side-drawer-container { .side-drawer-container {
position: fixed; position: fixed;
z-index: 1000; z-index: var(--ZI_navbar);
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;

View File

@ -17,8 +17,8 @@ const StaffPanel = {
const groupedStaffAccounts = groupBy(staffAccounts, 'role') const groupedStaffAccounts = groupBy(staffAccounts, 'role')
return [ return [
{ role: 'admin', users: groupedStaffAccounts['admin'] }, { role: 'admin', users: groupedStaffAccounts.admin },
{ role: 'moderator', users: groupedStaffAccounts['moderator'] } { role: 'moderator', users: groupedStaffAccounts.moderator }
].filter(group => group.users) ].filter(group => group.users)
}, },
...mapGetters([ ...mapGetters([

View File

@ -24,7 +24,7 @@
</div> </div>
</template> </template>
<script src="./staff_panel.js" ></script> <script src="./staff_panel.js"></script>
<style lang="scss"> <style lang="scss">

View File

@ -4,13 +4,13 @@ import ReactButton from '../react_button/react_button.vue'
import RetweetButton from '../retweet_button/retweet_button.vue' import RetweetButton from '../retweet_button/retweet_button.vue'
import ExtraButtons from '../extra_buttons/extra_buttons.vue' import ExtraButtons from '../extra_buttons/extra_buttons.vue'
import PostStatusForm from '../post_status_form/post_status_form.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 UserAvatar from '../user_avatar/user_avatar.vue'
import AvatarList from '../avatar_list/avatar_list.vue' import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue' import Timeago from '../timeago/timeago.vue'
import StatusContent from '../status_content/status_content.vue' import StatusContent from '../status_content/status_content.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import StatusPopover from '../status_popover/status_popover.vue' 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 UserListPopover from '../user_list_popover/user_list_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue' import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue' import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
@ -105,7 +105,6 @@ const Status = {
RetweetButton, RetweetButton,
ExtraButtons, ExtraButtons,
PostStatusForm, PostStatusForm,
UserCard,
UserAvatar, UserAvatar,
AvatarList, AvatarList,
Timeago, Timeago,
@ -115,7 +114,8 @@ const Status = {
StatusContent, StatusContent,
RichContent, RichContent,
MentionLink, MentionLink,
MentionsLine MentionsLine,
UserPopover
}, },
props: [ props: [
'statusoid', 'statusoid',
@ -361,6 +361,7 @@ const Status = {
return uniqBy(combinedUsers, 'id') return uniqBy(combinedUsers, 'id')
}, },
tags () { tags () {
// eslint-disable-next-line no-prototype-builtins
return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ') return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
}, },
hidePostStats () { hidePostStats () {
@ -448,7 +449,7 @@ const Status = {
scrollIfHighlighted (highlightId) { scrollIfHighlighted (highlightId) {
const id = highlightId const id = highlightId
if (this.status.id === id) { if (this.status.id === id) {
let rect = this.$el.getBoundingClientRect() const rect = this.$el.getBoundingClientRect()
if (rect.top < 100) { if (rect.top < 100) {
// Post is above screen, match its top to screen top // Post is above screen, match its top to screen top
window.scrollBy(0, rect.top - 100) window.scrollBy(0, rect.top - 100)
@ -463,7 +464,7 @@ const Status = {
} }
}, },
watch: { watch: {
'highlight': function (id) { highlight: function (id) {
this.scrollIfHighlighted(id) this.scrollIfHighlighted(id)
}, },
'status.repeat_num': function (num) { 'status.repeat_num': function (num) {
@ -478,7 +479,7 @@ const Status = {
this.$store.dispatch('fetchFavs', this.status.id) this.$store.dispatch('fetchFavs', this.status.id)
} }
}, },
'isSuspendable': function (val) { isSuspendable: function (val) {
this.suspendable = val this.suspendable = val
} }
} }

View File

@ -124,25 +124,23 @@
> >
<a <a
:href="$router.resolve(userProfileLink).href" :href="$router.resolve(userProfileLink).href"
@click.stop.prevent.capture="toggleUserExpanded" @click.prevent
> >
<UserAvatar <UserPopover
class="post-avatar" :user-id="status.user.id"
:bot="botIndicator" :overlay-centers="true"
:compact="compact" >
:better-shadow="betterShadow" <UserAvatar
:user="status.user" class="post-avatar"
/> :bot="botIndicator"
:compact="compact"
:better-shadow="betterShadow"
:user="status.user"
/>
</UserPopover>
</a> </a>
</div> </div>
<div class="right-side"> <div class="right-side">
<UserCard
v-if="userExpanded"
:user-id="status.user.id"
:rounded="true"
:bordered="true"
class="usercard"
/>
<div <div
v-if="!noHeading" v-if="!noHeading"
class="status-heading" class="status-heading"
@ -322,6 +320,7 @@
class="mentions-line-first" class="mentions-line-first"
/> />
</span> </span>
{{ ' ' }}
<MentionsLine <MentionsLine
v-if="hasMentionsLine" v-if="hasMentionsLine"
:mentions="mentionsLine.slice(1)" :mentions="mentionsLine.slice(1)"
@ -492,6 +491,6 @@
</div> </div>
</template> </template>
<script src="./status.js" ></script> <script src="./status.js"></script>
<style src="./status.scss" lang="scss"></style> <style src="./status.scss" lang="scss"></style>

View File

@ -96,5 +96,5 @@
<slot v-if="!hideSubjectStatus" /> <slot v-if="!hideSubjectStatus" />
</div> </div>
</template> </template>
<script src="./status_body.js" ></script> <script src="./status_body.js"></script>
<style lang="scss" src="./status_body.scss" /> <style lang="scss" src="./status_body.scss" />

Some files were not shown because too many files have changed in this diff Show More