Compare commits

..

23 commits

Author SHA1 Message Date
Ilja
e594252668 clean up leftover
There was a comment to enable something for eslint. This was a leftover from when it was dissabled.
But the dissabling was removed in a privious commit f9393b0dab
2022-03-21 09:59:52 +01:00
Ilja
88ad5033a8 Use empty array for emji instead
Instead of relying on a key that isn't actually usefull, I just provide an empty array directly.
2022-03-21 09:49:06 +01:00
Ilja
89c409c6d2 Add default array for RichContent emoji
Reports don't currently return an emoji key. There's an MR to add it, but in case something doesn't return this key, we now have a default empty array.
2022-03-21 08:57:59 +01:00
Ilja
d0bfd9a808 Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma-fe into feat/report-notification 2022-03-20 09:53:57 +01:00
Ilja
f9393b0dab Use RichContent component for Reports
Note that this expects an emoji list for the reports. I made an MR in BE to provide that: https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3650
2022-03-20 09:45:35 +01:00
Ilja
1d42d05c1f Use Select component
After merging develop, the dropdown didn't format properly any more because selects have been made into a component. Here I turn the select into a component as well.
2022-03-20 09:31:36 +01:00
Ilja
c9e4b6e7a1 Make linter happy
`npm run lint` gave warnings for two files, fixed them with `./node_modules/.bin/eslint --fix $FILENAME`
2022-02-26 02:13:43 +01:00
Ilja
d0c4ad22cd Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma-fe into feat/report-notification 2022-02-26 02:08:13 +01:00
Ilja
819b760261 Fix up and code review
* Check if it works properly
    * Notifs are shown as BE returns them
    * The Interaction view has Reports, but only when you're mod or admin
* Do some extra translations
* Fix some console spam
2022-02-26 01:53:01 +01:00
Shpuld Shpuldson
52c22e863e Fix setting report state, add proper error handling 2021-02-03 12:02:37 +02:00
Shpuld Shpuldson
3e6309ef94 remove logs 2021-02-01 14:43:01 +02:00
Shpuld Shpuldson
3822a73a49 Fix report modal not working, add include_types 2021-02-01 12:55:23 +02:00
Shpuld Shpuldson
8334649c11 Merge branch 'develop' into feat/report-notification 2021-01-27 13:24:19 +02:00
Shpuld Shpuldson
54def7d210 remove mock data 2021-01-27 13:13:59 +02:00
Shpuld Shpuldson
06f795d1d6 add proper state switcher 2021-01-27 13:13:10 +02:00
Shpuld Shpuldson
e73553dca7 wip 2021-01-18 15:26:08 +02:00
Shpuld Shpuldson
9613f80f8e Merge branch 'develop' into feat/report-notification 2021-01-13 12:53:53 +02:00
Shpuld Shpuldson
8674f20023 separated component 2021-01-11 19:32:58 +02:00
Shpuld Shpuldson
a4e3cccf1c somewhat workign version still with fixture 2021-01-06 18:31:34 +02:00
Shpuld Shpuldson
ab2c2c66bf Merge branch 'develop' into feat/report-notification 2021-01-05 13:58:52 +02:00
Shpuld Shpuldson
5e96260a4f add test data for dev 2020-12-04 12:48:15 +02:00
Shpuld Shpuldson
1fd1553a1c Merge branch 'develop' into feat/report-notification 2020-12-04 11:20:01 +02:00
Shpuld Shpuldson
15bed586dc report notification wip 2020-11-15 13:57:02 +02:00
148 changed files with 2327 additions and 2519 deletions

View file

@ -1,5 +1,5 @@
{ {
"presets": ["@babel/preset-env"], "presets": ["@babel/preset-env", "@vue/babel-preset-jsx"],
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"], "plugins": ["@babel/plugin-transform-runtime", "lodash"],
"comments": false "comments": false
} }

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:10
stages: stages:
- lint - lint

View file

@ -4,7 +4,6 @@ var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../') 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 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,16 +29,16 @@ module.exports = {
} }
}, },
resolve: { resolve: {
extensions: ['.js', '.jsx', '.vue'], extensions: ['.js', '.vue'],
modules: [ modules: [
path.join(__dirname, '../node_modules') path.join(__dirname, '../node_modules')
], ],
alias: { alias: {
'vue$': 'vue/dist/vue.runtime.common',
'static': path.resolve(__dirname, '../static'), 'static': path.resolve(__dirname, '../static'),
'src': path.resolve(__dirname, '../src'), 'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'), 'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components'), 'components': path.resolve(__dirname, '../src/components')
'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js'
} }
}, },
module: { module: {
@ -59,28 +58,9 @@ module.exports = {
} }
} }
}, },
{
enforce: 'post',
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
type: 'javascript/auto',
loader: '@intlify/vue-i18n-loader',
include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled
path.resolve(__dirname, '../src/i18n')
]
},
{ {
test: /\.vue$/, test: /\.vue$/,
loader: 'vue-loader', use: 'vue-loader'
options: {
compilerOptions: {
isCustomElement(tag) {
if (tag === 'pinch-zoom') {
return true
}
return false
}
}
}
}, },
{ {
test: /\.jsx?$/, test: /\.jsx?$/,
@ -115,7 +95,6 @@ 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 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: [

View file

@ -21,9 +21,7 @@ module.exports = merge(baseWebpackConfig, {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': config.dev.env, 'process.env': config.dev.env,
'COMMIT_HASH': JSON.stringify('DEV'), 'COMMIT_HASH': JSON.stringify('DEV'),
'DEV_OVERRIDES': JSON.stringify(config.dev.settings), 'DEV_OVERRIDES': JSON.stringify(config.dev.settings)
'__VUE_OPTIONS_API__': true,
'__VUE_PROD_DEVTOOLS__': false
}), }),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),

View file

@ -36,9 +36,7 @@ var webpackConfig = merge(baseWebpackConfig, {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': env, 'process.env': env,
'COMMIT_HASH': JSON.stringify(commitHash), 'COMMIT_HASH': JSON.stringify(commitHash),
'DEV_OVERRIDES': JSON.stringify(undefined), 'DEV_OVERRIDES': JSON.stringify(undefined)
'__VUE_OPTIONS_API__': true,
'__VUE_PROD_DEVTOOLS__': false
}), }),
// extract css into its own file // extract css into its own file
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({

View file

@ -52,10 +52,7 @@ module.exports = {
target, target,
changeOrigin: true, changeOrigin: true,
cookieDomainRewrite: 'localhost', cookieDomainRewrite: 'localhost',
ws: true, ws: true
headers: {
'Origin': target
}
}, },
'/oauth/revoke': { '/oauth/revoke': {
target, target,

View file

@ -16,47 +16,44 @@
"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.17.8", "@babel/runtime": "7.17.7",
"@chenfengyuan/vue-qrcode": "2.0.0", "@chenfengyuan/vue-qrcode": "1.0.2",
"@fortawesome/fontawesome-svg-core": "1.3.0", "@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "3.0.0-5", "@fortawesome/vue-fontawesome": "2.0.6",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0", "@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@vuelidate/core": "2.0.0-alpha.35",
"@vuelidate/validators": "2.0.0-alpha.27",
"body-scroll-lock": "2.7.1", "body-scroll-lock": "2.7.1",
"chromatism": "3.0.0", "chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
"cropperjs": "1.5.12", "cropperjs": "1.5.12",
"diff": "3.5.0", "diff": "3.5.0",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"localforage": "1.10.0", "localforage": "1.7.3",
"parse-link-header": "1.0.1", "parse-link-header": "1.0.1",
"phoenix": "1.6.2", "phoenix": "1.4.0",
"portal-vue": "2.1.7",
"punycode.js": "2.1.0", "punycode.js": "2.1.0",
"qrcode": "1", "ruffle-mirror": "2021.4.11",
"ruffle-mirror": "2021.12.31", "v-click-outside": "2.1.5",
"vue": "^3.2.31", "vue": "2.6.11",
"vue-i18n": "^9.2.0-beta.34", "vue-i18n": "7.8.1",
"vue-router": "4.0.14", "vue-router": "3.0.2",
"vue-template-compiler": "2.6.11", "vue-template-compiler": "2.6.11",
"vuex": "4.0.2" "vuelidate": "0.7.7",
"vuex": "3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.17.8", "@babel/core": "7.17.7",
"@babel/plugin-transform-runtime": "7.17.0", "@babel/plugin-transform-runtime": "7.17.0",
"@babel/preset-env": "7.16.11", "@babel/preset-env": "7.16.11",
"@babel/register": "7.17.7", "@babel/register": "7.17.7",
"@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-preset-jsx": "1.2.4",
"@vue/compiler-sfc": "^3.1.0", "@vue/test-utils": "1.0.0-beta.28",
"@vue/test-utils": "2.0.0-rc.17",
"autoprefixer": "6.7.7", "autoprefixer": "6.7.7",
"babel-eslint": "7.2.3", "babel-eslint": "7.2.3",
"babel-loader": "8.2.4", "babel-loader": "8.2.3",
"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",
@ -69,26 +66,26 @@
"eslint": "5.16.0", "eslint": "5.16.0",
"eslint-config-standard": "12.0.0", "eslint-config-standard": "12.0.0",
"eslint-friendly-formatter": "2.0.7", "eslint-friendly-formatter": "2.0.7",
"eslint-loader": "2.2.1", "eslint-loader": "2.1.2",
"eslint-plugin-import": "2.25.4", "eslint-plugin-import": "2.17.2",
"eslint-plugin-node": "7.0.1", "eslint-plugin-node": "7.0.1",
"eslint-plugin-promise": "4.3.1", "eslint-plugin-promise": "4.1.1",
"eslint-plugin-standard": "4.1.0", "eslint-plugin-standard": "4.0.0",
"eslint-plugin-vue": "5.2.3", "eslint-plugin-vue": "5.2.3",
"eventsource-polyfill": "0.9.6", "eventsource-polyfill": "0.9.6",
"express": "4.17.3", "express": "4.16.4",
"file-loader": "3.0.1", "file-loader": "3.0.1",
"function-bind": "1.1.1", "function-bind": "1.1.1",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"http-proxy-middleware": "0.21.0", "http-proxy-middleware": "0.17.4",
"inject-loader": "2.0.1", "inject-loader": "2.0.1",
"iso-639-1": "2.1.13", "iso-639-1": "2.0.3",
"isparta-loader": "2.0.0", "isparta-loader": "2.0.0",
"json-loader": "0.5.7", "json-loader": "0.5.7",
"karma": "6.3.17", "karma": "3.1.4",
"karma-coverage": "1.1.2", "karma-coverage": "1.1.2",
"karma-firefox-launcher": "1.3.0", "karma-firefox-launcher": "1.1.0",
"karma-mocha": "2.0.1", "karma-mocha": "1.3.0",
"karma-mocha-reporter": "2.2.5", "karma-mocha-reporter": "2.2.5",
"karma-sinon-chai": "2.0.2", "karma-sinon-chai": "2.0.2",
"karma-sourcemap-loader": "0.3.8", "karma-sourcemap-loader": "0.3.8",
@ -96,11 +93,11 @@
"karma-webpack": "4.0.2", "karma-webpack": "4.0.2",
"lodash": "4.17.21", "lodash": "4.17.21",
"lolex": "1.6.0", "lolex": "1.6.0",
"mini-css-extract-plugin": "0.12.0", "mini-css-extract-plugin": "0.5.0",
"mocha": "3.5.3", "mocha": "3.5.3",
"nightwatch": "0.9.21", "nightwatch": "0.9.21",
"opn": "4.0.2", "opn": "4.0.2",
"ora": "0.4.1", "ora": "0.3.0",
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"sass": "1.20.1", "sass": "1.20.1",
@ -115,7 +112,7 @@
"stylelint-config-standard": "20.0.0", "stylelint-config-standard": "20.0.0",
"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": "14.2.4",
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.2",
"webpack": "4.46.0", "webpack": "4.46.0",
"webpack-dev-middleware": "3.7.3", "webpack-dev-middleware": "3.7.3",

View file

@ -46,7 +46,7 @@ export default {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
window.addEventListener('resize', this.updateMobileState) window.addEventListener('resize', this.updateMobileState)
}, },
unmounted () { destroyed () {
window.removeEventListener('resize', this.updateMobileState) window.removeEventListener('resize', this.updateMobileState)
}, },
computed: { computed: {
@ -65,7 +65,7 @@ export default {
} }
} }
}, },
shout () { return this.$store.state.shout.joined }, shout () { return this.$store.state.shout.channel.state === 'joined' },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel && return this.$store.state.instance.showInstanceSpecificPanel &&

View file

@ -572,7 +572,7 @@ nav {
.fade-enter-active, .fade-leave-active { .fade-enter-active, .fade-leave-active {
transition: opacity .2s transition: opacity .2s
} }
.fade-enter-from, .fade-leave-active { .fade-enter, .fade-leave-active {
opacity: 0 opacity: 0
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
id="app-loaded" id="app"
:style="bgStyle" :style="bgStyle"
> >
<div <div
@ -59,7 +59,7 @@
<UserReportingModal /> <UserReportingModal />
<PostStatusModal /> <PostStatusModal />
<SettingsModal /> <SettingsModal />
<div id="modal" /> <portal-target name="modal" />
<GlobalNoticeList /> <GlobalNoticeList />
</div> </div>
</template> </template>

View file

@ -1,13 +1,7 @@
import { createApp } from 'vue' import Vue from 'vue'
import { createRouter, createWebHistory } from 'vue-router' import VueRouter from 'vue-router'
import vClickOutside from 'click-outside-vue3'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import App from '../App.vue'
import routes from './routes' import routes from './routes'
import VBodyScrollLock from 'src/directives/body_scroll_lock' import App from '../App.vue'
import { windowWidth } from '../services/window_utils/window_utils' import { windowWidth } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
@ -373,32 +367,25 @@ const afterStoreSetup = async ({ store, i18n }) => {
getTOS({ store }) getTOS({ store })
getStickers({ store }) getStickers({ store })
const router = createRouter({ const router = new VueRouter({
history: createWebHistory(), mode: 'history',
routes: routes(store), routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => { scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) { if (to.matched.some(m => m.meta.dontScroll)) {
return false return false
} }
return savedPosition || { left: 0, top: 0 } return savedPosition || { x: 0, y: 0 }
} }
}) })
const app = createApp(App) /* eslint-disable no-new */
return new Vue({
app.use(router) router,
app.use(store) store,
app.use(i18n) i18n,
el: '#app',
app.use(vClickOutside) render: h => h(App)
app.use(VBodyScrollLock) })
app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers)
app.mount('#app')
return app
} }
export default afterStoreSetup export default afterStoreSetup

View file

@ -46,7 +46,7 @@ export default (store) => {
{ 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
}, },
@ -69,7 +69,7 @@ export default (store) => {
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) }, { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute }, { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About }, { name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile } { name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
] ]
if (store.state.instance.pleromaChatMessagesAvailable) { if (store.state.instance.pleromaChatMessagesAvailable) {

View file

@ -19,7 +19,6 @@
<script> <script>
export default { export default {
emits: ['resetAsyncComponent'],
methods: { methods: {
retry () { retry () {
this.$emit('resetAsyncComponent') this.$emit('resetAsyncComponent')

View file

@ -1,4 +1,3 @@
import { h, resolveComponent } from 'vue'
import LoginForm from '../login_form/login_form.vue' import LoginForm from '../login_form/login_form.vue'
import MFARecoveryForm from '../mfa_form/recovery_form.vue' import MFARecoveryForm from '../mfa_form/recovery_form.vue'
import MFATOTPForm from '../mfa_form/totp_form.vue' import MFATOTPForm from '../mfa_form/totp_form.vue'
@ -6,8 +5,8 @@ import { mapGetters } from 'vuex'
const AuthForm = { const AuthForm = {
name: 'AuthForm', name: 'AuthForm',
render () { render (createElement) {
return h(resolveComponent(this.authForm)) return createElement('component', { is: this.authForm })
}, },
computed: { computed: {
authForm () { authForm () {

View file

@ -4,7 +4,7 @@
<UserAvatar <UserAvatar
class="avatar" class="avatar"
:user="user" :user="user"
@click.prevent="toggleUserExpanded" @click.prevent.native="toggleUserExpanded"
/> />
</router-link> </router-link>
<div <div

View file

@ -9,7 +9,7 @@ const Bookmarks = {
components: { components: {
Timeline Timeline
}, },
unmounted () { destroyed () {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
} }
} }

View file

@ -57,7 +57,7 @@ const Chat = {
}) })
this.setChatLayout() this.setChatLayout()
}, },
unmounted () { destroyed () {
window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleLayoutChange) window.removeEventListener('resize', this.handleLayoutChange)
this.unsetChatLayout() this.unsetChatLayout()

View file

@ -26,71 +26,73 @@
/> />
</div> </div>
</div> </div>
<div <template>
ref="scrollable"
class="scrollable-message-list"
:style="{ height: scrollableContainerHeight }"
@scroll="handleScroll"
>
<template v-if="!errorLoadingChat">
<ChatMessage
v-for="chatViewItem in chatViewItems"
:key="chatViewItem.id"
:author="recipient"
:chat-view-item="chatViewItem"
:hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId"
@hover="onMessageHover"
/>
</template>
<div <div
v-else ref="scrollable"
class="chat-loading-error" class="scrollable-message-list"
:style="{ height: scrollableContainerHeight }"
@scroll="handleScroll"
> >
<div class="alert error"> <template v-if="!errorLoadingChat">
{{ $t('chats.error_loading_chat') }} <ChatMessage
v-for="chatViewItem in chatViewItems"
:key="chatViewItem.id"
:author="recipient"
:chat-view-item="chatViewItem"
:hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId"
@hover="onMessageHover"
/>
</template>
<div
v-else
class="chat-loading-error"
>
<div class="alert error">
{{ $t('chats.error_loading_chat') }}
</div>
</div> </div>
</div> </div>
</div>
<div
ref="footer"
class="panel-body footer"
>
<div <div
class="jump-to-bottom-button" ref="footer"
:class="{ 'visible': jumpToBottomButtonVisible }" class="panel-body footer"
@click="scrollDown({ behavior: 'smooth' })"
> >
<span> <div
<FAIcon icon="chevron-down" /> class="jump-to-bottom-button"
<div :class="{ 'visible': jumpToBottomButtonVisible }"
v-if="newMessageCount" @click="scrollDown({ behavior: 'smooth' })"
class="badge badge-notification unread-chat-count unread-message-count" >
> <span>
{{ newMessageCount }} <FAIcon icon="chevron-down" />
</div> <div
</span> v-if="newMessageCount"
class="badge badge-notification unread-chat-count unread-message-count"
>
{{ newMessageCount }}
</div>
</span>
</div>
<PostStatusForm
:disable-subject="true"
:disable-scope-selector="true"
:disable-notice="true"
:disable-lock-warning="true"
:disable-polls="true"
:disable-sensitivity-checkbox="true"
:disable-submit="errorLoadingChat || !currentChat"
:disable-preview="true"
:optimistic-posting="true"
:post-handler="sendMessage"
:submit-on-enter="!mobileLayout"
:preserve-focus="!mobileLayout"
:auto-focus="!mobileLayout"
:placeholder="formPlaceholder"
:file-limit="1"
max-height="160"
emoji-picker-placement="top"
@resize="handleResize"
/>
</div> </div>
<PostStatusForm </template>
:disable-subject="true"
:disable-scope-selector="true"
:disable-notice="true"
:disable-lock-warning="true"
:disable-polls="true"
:disable-sensitivity-checkbox="true"
:disable-submit="errorLoadingChat || !currentChat"
:disable-preview="true"
:optimistic-posting="true"
:post-handler="sendMessage"
:submit-on-enter="!mobileLayout"
:preserve-focus="!mobileLayout"
:auto-focus="!mobileLayout"
:placeholder="formPlaceholder"
:file-limit="1"
max-height="160"
emoji-picker-placement="top"
@resize="handleResize"
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -27,7 +27,6 @@ const ChatMessage = {
'chatViewItem', 'chatViewItem',
'hoveredMessageChain' 'hoveredMessageChain'
], ],
emits: ['hover'],
components: { components: {
Popover, Popover,
Attachment, Attachment,

View file

@ -1,12 +1,11 @@
import Vue from 'vue'
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'
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'
export default { export default Vue.component('chat-title', {
name: 'ChatTitle', name: 'ChatTitle',
components: { components: {
UserAvatar, UserAvatar
RichContent
}, },
props: [ props: [
'user', 'withAvatar' 'user', 'withAvatar'
@ -24,4 +23,4 @@ export default {
return generateProfileLink(user.id, user.screen_name) return generateProfileLink(user.id, user.screen_name)
} }
} }
} })

View file

@ -14,11 +14,10 @@
/> />
</router-link> </router-link>
<RichContent <RichContent
v-if="user"
class="username" class="username"
:title="'@'+user.screen_name_ui" :title="'@'+user.screen_name_ui"
:html="htmlTitle" :html="htmlTitle"
:emoji="user.emoji || []" :emoji="user.emoji"
/> />
</div> </div>
</template> </template>

View file

@ -6,9 +6,9 @@
<input <input
type="checkbox" type="checkbox"
:disabled="disabled" :disabled="disabled"
:checked="modelValue" :checked="checked"
:indeterminate="indeterminate" :indeterminate.prop="indeterminate"
@change="$emit('update:modelValue', $event.target.checked)" @change="$emit('change', $event.target.checked)"
> >
<i class="checkbox-indicator" /> <i class="checkbox-indicator" />
<span <span
@ -22,9 +22,12 @@
<script> <script>
export default { export default {
emits: ['update:modelValue'], model: {
prop: 'checked',
event: 'change'
},
props: [ props: [
'modelValue', 'checked',
'indeterminate', 'indeterminate',
'disabled' 'disabled'
] ]

View file

@ -11,28 +11,28 @@
</label> </label>
<Checkbox <Checkbox
v-if="typeof fallback !== 'undefined' && showOptionalTickbox" v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
:model-value="present" :checked="present"
:disabled="disabled" :disabled="disabled"
class="opt" class="opt"
@update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)" @change="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
/> />
<div class="input color-input-field"> <div class="input color-input-field">
<input <input
:id="name + '-t'" :id="name + '-t'"
class="textColor unstyled" class="textColor unstyled"
type="text" type="text"
:value="modelValue || fallback" :value="value || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('input', $event.target.value)"
> >
<input <input
v-if="validColor" v-if="validColor"
:id="name" :id="name"
class="nativeColor unstyled" class="nativeColor unstyled"
type="color" type="color"
:value="modelValue || fallback" :value="value || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('input', $event.target.value)"
> >
<div <div
v-if="transparentColor" v-if="transparentColor"
@ -67,7 +67,7 @@ export default {
}, },
// Color value, should be required but vue cannot tell the difference // Color value, should be required but vue cannot tell the difference
// between "property missing" and "property set to undefined" // between "property missing" and "property set to undefined"
modelValue: { value: {
required: false, required: false,
type: String, type: String,
default: undefined default: undefined
@ -91,19 +91,18 @@ export default {
default: true default: true
} }
}, },
emits: ['update:modelValue'],
computed: { computed: {
present () { present () {
return typeof this.modelValue !== 'undefined' return typeof this.value !== 'undefined'
}, },
validColor () { validColor () {
return hex2rgb(this.modelValue || this.fallback) return hex2rgb(this.value || this.fallback)
}, },
transparentColor () { transparentColor () {
return this.modelValue === 'transparent' return this.value === 'transparent'
}, },
computedColor () { computedColor () {
return this.modelValue && this.modelValue.startsWith('--') return this.value && this.value.startsWith('--')
} }
} }
} }

View file

@ -27,24 +27,20 @@
v-if="shouldShowAllConversationButton" v-if="shouldShowAllConversationButton"
class="conversation-dive-to-top-level-box" class="conversation-dive-to-top-level-box"
> >
<i18n-t <i18n
keypath="status.show_all_conversation_with_icon" path="status.show_all_conversation_with_icon"
tag="button" tag="button"
class="button-unstyled -link" class="button-unstyled -link"
@click.prevent="diveToTopLevel" @click.prevent="diveToTopLevel"
scope="global"
> >
<template #icon> <FAIcon
<FAIcon place="icon"
icon="angle-double-left" icon="angle-double-left"
/> />
</template> <span place="text">
<template #text> {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }}
<span> </span>
{{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }} </i18n>
</span>
</template>
</i18n-t>
</div> </div>
<div <div
v-if="shouldShowAncestors" v-if="shouldShowAncestors"
@ -100,24 +96,20 @@
<div <div
class="thread-ancestor-dive-box-inner" class="thread-ancestor-dive-box-inner"
> >
<i18n-t <i18n
tag="button" tag="button"
scope="global" path="status.ancestor_follow_with_icon"
keypath="status.ancestor_follow_with_icon"
class="button-unstyled -link thread-tree-show-replies-button" class="button-unstyled -link thread-tree-show-replies-button"
@click.prevent="diveIntoStatus(status.id)" @click.prevent="diveIntoStatus(status.id)"
> >
<template #icon> <FAIcon
<FAIcon place="icon"
icon="angle-double-right" icon="angle-double-right"
/> />
</template> <span place="text">
<template #text> {{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }}
<span> </span>
{{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }} </i18n>
</span>
</template>
</i18n-t>
</div> </div>
</div> </div>
</div> </div>

View file

@ -34,7 +34,7 @@
<search-bar <search-bar
v-if="currentUser || !privateMode" v-if="currentUser || !privateMode"
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop @click.stop.native
/> />
<button <button
class="button-unstyled nav-icon" class="button-unstyled nav-icon"

View file

@ -31,7 +31,6 @@ library.add(
*/ */
const EmojiInput = { const EmojiInput = {
emits: ['update:modelValue', 'shown'],
props: { props: {
suggest: { suggest: {
/** /**
@ -58,7 +57,8 @@ const EmojiInput = {
required: true, required: true,
type: Function type: Function
}, },
modelValue: { // TODO VUE3: change to modelValue, change 'input' event to 'input'
value: {
/** /**
* Used for v-model * Used for v-model
*/ */
@ -137,8 +137,8 @@ const EmojiInput = {
return (this.wordAtCaret || {}).word || '' return (this.wordAtCaret || {}).word || ''
}, },
wordAtCaret () { wordAtCaret () {
if (this.modelValue && this.caret) { if (this.value && this.caret) {
const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {} const word = Completion.wordAtPosition(this.value, this.caret - 1) || {}
return word return word
} }
} }
@ -189,11 +189,8 @@ const EmojiInput = {
img: imageUrl || '' img: imageUrl || ''
})) }))
}, },
suggestions: { suggestions (newValue) {
handler (newValue) { this.$nextTick(this.resize)
this.$nextTick(this.resize)
},
deep: true
} }
}, },
methods: { methods: {
@ -228,13 +225,13 @@ const EmojiInput = {
} }
}, },
replace (replacement) { replace (replacement) {
const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement) const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
this.$emit('update:modelValue', newValue) this.$emit('input', newValue)
this.caret = 0 this.caret = 0
}, },
insert ({ insertion, keepOpen, surroundingSpace = true }) { insert ({ insertion, keepOpen, surroundingSpace = true }) {
const before = this.modelValue.substring(0, this.caret) || '' const before = this.value.substring(0, this.caret) || ''
const after = this.modelValue.substring(this.caret) || '' const after = this.value.substring(this.caret) || ''
/* Using a bit more smart approach to padding emojis with spaces: /* Using a bit more smart approach to padding emojis with spaces:
* - put a space before cursor if there isn't one already, unless we * - put a space before cursor if there isn't one already, unless we
@ -262,7 +259,7 @@ const EmojiInput = {
after after
].join('') ].join('')
this.keepOpen = keepOpen this.keepOpen = keepOpen
this.$emit('update:modelValue', newValue) this.$emit('input', newValue)
const position = this.caret + (insertion + spaceAfter + spaceBefore).length const position = this.caret + (insertion + spaceAfter + spaceBefore).length
if (!keepOpen) { if (!keepOpen) {
this.input.focus() this.input.focus()
@ -281,8 +278,8 @@ const EmojiInput = {
if (len > 0 || suggestion) { if (len > 0 || suggestion) {
const chosenSuggestion = suggestion || this.suggestions[this.highlighted] const chosenSuggestion = suggestion || this.suggestions[this.highlighted]
const replacement = chosenSuggestion.replacement const replacement = chosenSuggestion.replacement
const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement) const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
this.$emit('update:modelValue', newValue) this.$emit('input', newValue)
this.highlighted = 0 this.highlighted = 0
const position = this.wordAtCaret.start + replacement.length const position = this.wordAtCaret.start + replacement.length
@ -458,7 +455,7 @@ const EmojiInput = {
this.showPicker = false this.showPicker = false
this.setCaret(e) this.setCaret(e)
this.resize() this.resize()
this.$emit('update:modelValue', e.target.value) this.$emit('input', e.target.value)
}, },
onClickInput (e) { onClickInput (e) {
this.showPicker = false this.showPicker = false

View file

@ -1,4 +1,3 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -58,7 +57,7 @@ const EmojiPicker = {
} }
}, },
components: { components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')), StickerPicker: () => import('../sticker_picker/sticker_picker.vue'),
Checkbox Checkbox
}, },
methods: { methods: {
@ -80,7 +79,7 @@ const EmojiPicker = {
}, },
highlight (key) { highlight (key) {
const ref = this.$refs['group-' + key] const ref = this.$refs['group-' + key]
const top = ref.offsetTop const top = ref[0].offsetTop
this.setShowStickers(false) this.setShowStickers(false)
this.activeGroup = key this.activeGroup = key
this.$nextTick(() => { this.$nextTick(() => {
@ -97,7 +96,7 @@ const EmojiPicker = {
} }
}, },
triggerLoadMore (target) { triggerLoadMore (target) {
const ref = this.$refs['group-end-custom'] const ref = this.$refs['group-end-custom'][0]
if (!ref) return if (!ref) return
const bottom = ref.offsetTop + ref.offsetHeight const bottom = ref.offsetTop + ref.offsetHeight
@ -120,7 +119,7 @@ const EmojiPicker = {
this.$nextTick(() => { this.$nextTick(() => {
this.emojisView.forEach(group => { this.emojisView.forEach(group => {
const ref = this.$refs['group-' + group.id] const ref = this.$refs['group-' + group.id]
if (ref.offsetTop <= top) { if (ref[0].offsetTop <= top) {
this.activeGroup = group.id this.activeGroup = group.id
} }
}) })

View file

@ -15,8 +15,18 @@ const Exporter = {
type: String, type: String,
default: 'export.csv' default: 'export.csv'
}, },
exportButtonLabel: { type: String }, exportButtonLabel: {
processingMessage: { type: String } type: String,
default () {
return this.$t('exporter.export')
}
},
processingMessage: {
type: String,
default () {
return this.$t('exporter.processing')
}
}
}, },
data () { data () {
return { return {

View file

@ -7,14 +7,14 @@
spin spin
/> />
<span>{{ processingMessage || $t('exporter.processing') }}</span> <span>{{ processingMessage }}</span>
</div> </div>
<button <button
v-else v-else
class="btn button-default" class="btn button-default"
@click="process" @click="process"
> >
{{ exportButtonLabel || $t('exporter.export') }} {{ exportButtonLabel }}
</button> </button>
</div> </div>
</template> </template>

View file

@ -1,4 +1,4 @@
import { set } from 'lodash' import { set } from 'vue'
import Select from '../select/select.vue' import Select from '../select/select.vue'
export default { export default {
@ -6,12 +6,11 @@ export default {
Select Select
}, },
props: [ props: [
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit' 'name', 'label', 'value', 'fallback', 'options', 'no-inherit'
], ],
emits: ['update:modelValue'],
data () { data () {
return { return {
lValue: this.modelValue, lValue: this.value,
availableOptions: [ availableOptions: [
this.noInherit ? '' : 'inherit', this.noInherit ? '' : 'inherit',
'custom', 'custom',
@ -23,7 +22,7 @@ export default {
} }
}, },
beforeUpdate () { beforeUpdate () {
this.lValue = this.modelValue this.lValue = this.value
}, },
computed: { computed: {
present () { present () {
@ -38,7 +37,7 @@ export default {
}, },
set (v) { set (v) {
set(this.lValue, 'family', v) set(this.lValue, 'family', v)
this.$emit('update:modelValue', this.lValue) this.$emit('input', this.lValue)
} }
}, },
isCustom () { isCustom () {

View file

@ -15,14 +15,13 @@
class="opt exlcude-disabled" class="opt exlcude-disabled"
type="checkbox" type="checkbox"
:checked="present" :checked="present"
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)" @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
> >
<label <label
v-if="typeof fallback !== 'undefined'" v-if="typeof fallback !== 'undefined'"
class="opt-l" class="opt-l"
:for="name + '-o'" :for="name + '-o'"
/> />
{{ ' ' }}
<Select <Select
:id="name + '-font-switcher'" :id="name + '-font-switcher'"
v-model="preset" v-model="preset"

View file

@ -1,5 +1,5 @@
import Attachment from '../attachment/attachment.vue' import Attachment from '../attachment/attachment.vue'
import { sumBy, set } from 'lodash' import { sumBy } from 'lodash'
const Gallery = { const Gallery = {
props: [ props: [
@ -85,7 +85,7 @@ const Gallery = {
}, },
methods: { methods: {
onNaturalSizeLoad ({ id, width, height }) { onNaturalSizeLoad ({ id, width, height }) {
set(this.sizes, id, { width, height }) this.$set(this.sizes, id, { width, height })
}, },
rowStyle (row) { rowStyle (row) {
if (row.audio) { if (row.audio) {

View file

@ -22,6 +22,7 @@
class="gallery-item" class="gallery-item"
:nsfw="nsfw" :nsfw="nsfw"
:attachment="attachment" :attachment="attachment"
:allow-play="false"
:size="size" :size="size"
:editable="editable" :editable="editable"
:remove="removeAttachment" :remove="removeAttachment"

View file

@ -117,7 +117,7 @@ const ImageCropper = {
const fileInput = this.$refs.input const fileInput = this.$refs.input
fileInput.addEventListener('change', this.readFile) fileInput.addEventListener('change', this.readFile)
}, },
beforeUnmount: function () { beforeDestroy: function () {
// remove the event listeners // remove the event listeners
const trigger = this.getTriggerDOM() const trigger = this.getTriggerDOM()
if (trigger) { if (trigger) {

View file

@ -15,9 +15,24 @@ const Importer = {
type: Function, type: Function,
required: true required: true
}, },
submitButtonLabel: { type: String }, submitButtonLabel: {
successMessage: { type: String }, type: String,
errorMessage: { type: String } default () {
return this.$t('importer.submit')
}
},
successMessage: {
type: String,
default () {
return this.$t('importer.success')
}
},
errorMessage: {
type: String,
default () {
return this.$t('importer.error')
}
}
}, },
data () { data () {
return { return {

View file

@ -18,31 +18,21 @@
class="btn button-default" class="btn button-default"
@click="submit" @click="submit"
> >
{{ submitButtonLabel || $t('importer.submit') }} {{ submitButtonLabel }}
</button> </button>
<div v-if="success"> <div v-if="success">
<button <FAIcon
class="button-unstyled" icon="times"
@click="dismiss" @click="dismiss"
> />
<FAIcon <p>{{ successMessage }}</p>
icon="times"
/>
</button>
{{ ' ' }}
<span>{{ successMessage || $t('importer.success') }}</span>
</div> </div>
<div v-else-if="error"> <div v-else-if="error">
<button <FAIcon
class="button-unstyled" icon="times"
@click="dismiss" @click="dismiss"
> />
<FAIcon <p>{{ errorMessage }}</p>
icon="times"
/>
</button>
{{ ' ' }}
<span>{{ errorMessage || $t('importer.error') }}</span>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,10 +1,11 @@
import Notifications from '../notifications/notifications.vue' import Notifications from '../notifications/notifications.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
const tabModeDict = { const tabModeDict = {
mentions: ['mention'], mentions: ['mention'],
'likes+repeats': ['repeat', 'like'], 'likes+repeats': ['repeat', 'like'],
follows: ['follow'], follows: ['follow'],
reactions: ['pleroma:emoji_reaction'],
reports: ['pleroma:report'],
moves: ['move'] moves: ['move']
} }
@ -12,7 +13,8 @@ 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)
} }
}, },
methods: { methods: {
@ -21,8 +23,7 @@ const Interactions = {
} }
}, },
components: { components: {
Notifications, Notifications
TabSwitcher
} }
} }

View file

@ -21,6 +21,15 @@
key="follows" key="follows"
:label="$t('interactions.follows')" :label="$t('interactions.follows')"
/> />
<span
key="reactions"
:label="$t('interactions.emoji_reactions')"
/>
<span
v-if="canSeeReports"
key="reports"
:label="$t('interactions.reports')"
/>
<span <span
v-if="!allowFollowingMove" v-if="!allowFollowingMove"
key="moves" key="moves"

View file

@ -3,7 +3,6 @@
<label for="interface-language-switcher"> <label for="interface-language-switcher">
{{ $t('settings.interfaceLanguage') }} {{ $t('settings.interfaceLanguage') }}
</label> </label>
{{ ' ' }}
<Select <Select
id="interface-language-switcher" id="interface-language-switcher"
v-model="language" v-model="language"

View file

@ -76,15 +76,11 @@
> >
<div class="alert error"> <div class="alert error">
{{ error }} {{ error }}
<button <FAIcon
class="button-unstyled" class="fa-scale-110 fa-old-padding"
icon="times"
@click="clearError" @click="clearError"
> />
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
/>
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -142,7 +142,7 @@ const MediaModal = {
document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keyup', this.handleKeyupEvent)
document.addEventListener('keydown', this.handleKeydownEvent) document.addEventListener('keydown', this.handleKeydownEvent)
}, },
unmounted () { destroyed () {
window.removeEventListener('popstate', this.hide) window.removeEventListener('popstate', this.hide)
document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keyup', this.handleKeyupEvent)
document.removeEventListener('keydown', this.handleKeydownEvent) document.removeEventListener('keydown', this.handleKeydownEvent)

View file

@ -41,12 +41,10 @@
class="serverName" class="serverName"
:class="{ '-faded': shouldFadeDomain }" :class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName" v-html="'@' + serverName"
/> /></span><span
</span>
<span
v-if="isYou && shouldShowYous" v-if="isYou && shouldShowYous"
:class="{ '-you': shouldBoldenYou }" :class="{ '-you': shouldBoldenYou }"
> {{ ' ' + $t('status.you') }}</span> > {{ $t('status.you') }}</span>
<!-- eslint-enable vue/no-v-html --> <!-- eslint-enable vue/no-v-html -->
</a><span </a><span
v-if="shouldShowTooltip" v-if="shouldShowTooltip"

View file

@ -6,6 +6,7 @@
class="mention-link" class="mention-link"
:content="mention.content" :content="mention.content"
:url="mention.url" :url="mention.url"
:first-mention="false"
/><span /><span
v-if="manyMentions" v-if="manyMentions"
class="extraMentions" class="extraMentions"
@ -20,6 +21,7 @@
class="mention-link" class="mention-link"
:content="mention.content" :content="mention.content"
:url="mention.url" :url="mention.url"
:first-mention="false"
/> />
</span><button </span><button
v-if="!expanded" v-if="!expanded"

View file

@ -56,15 +56,11 @@
> >
<div class="alert error"> <div class="alert error">
{{ error }} {{ error }}
<button <FAIcon
class="button-unstyled" class="fa-scale-110 fa-old-padding"
icon="times"
@click="clearError" @click="clearError"
> />
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
/>
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -58,16 +58,12 @@
> >
<div class="alert error"> <div class="alert error">
{{ error }} {{ error }}
<button <FAIcon
class="button-unstyled" size="lg"
class="fa-scale-110 fa-old-padding"
icon="times"
@click="clearError" @click="clearError"
> />
<FAIcon
size="lg"
class="fa-scale-110 fa-old-padding"
icon="times"
/>
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -99,9 +99,6 @@
width: 100%; width: 100%;
position: fixed; position: fixed;
box-sizing: border-box; box-sizing: border-box;
a {
color: var(--topBarLink, $fallback--link);
}
} }
.mobile-inner-nav { .mobile-inner-nav {

View file

@ -29,7 +29,7 @@ const MobilePostStatusButton = {
} }
window.addEventListener('resize', this.handleOSK) window.addEventListener('resize', this.handleOSK)
}, },
unmounted () { destroyed () {
if (this.autohideFloatingPostButton) { if (this.autohideFloatingPostButton) {
this.deactivateFloatingPostButtonAutohide() this.deactivateFloatingPostButtonAutohide()
} }

View file

@ -1,12 +1,13 @@
<template> <template>
<button <div v-if="isLoggedIn">
v-if="isLoggedIn" <button
class="MobilePostButton button-default new-status-button" class="button-default new-status-button"
:class="{ 'hidden': isHidden, 'always-show': isPersistent }" :class="{ 'hidden': isHidden, 'always-show': isPersistent }"
@click="openPostForm" @click="openPostForm"
> >
<FAIcon icon="pen" /> <FAIcon icon="pen" />
</button> </button>
</div>
</template> </template>
<script src="./mobile_post_status_button.js"></script> <script src="./mobile_post_status_button.js"></script>
@ -14,27 +15,25 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.MobilePostButton { .new-status-button {
&.button-default { width: 5em;
width: 5em; height: 5em;
height: 5em; border-radius: 100%;
border-radius: 100%; position: fixed;
position: fixed; bottom: 1.5em;
bottom: 1.5em; right: 1.5em;
right: 1.5em; // TODO: this needs its own color, it has to stand out enough and link color
// TODO: this needs its own color, it has to stand out enough and link color // is not very optimal for this particular use.
// is not very optimal for this particular use. background-color: $fallback--fg;
background-color: $fallback--fg; background-color: var(--btn, $fallback--fg);
background-color: var(--btn, $fallback--fg); display: flex;
display: flex; justify-content: center;
justify-content: center; align-items: center;
align-items: center; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3); z-index: 10;
z-index: 10;
transition: 0.35s transform; transition: 0.35s transform;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1); transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
&.hidden { &.hidden {
transform: translateY(150%); transform: translateY(150%);

View file

@ -132,7 +132,7 @@
</button> </button>
</template> </template>
</Popover> </Popover>
<teleport to="#modal"> <portal to="modal">
<DialogModal <DialogModal
v-if="showDeleteUserDialog" v-if="showDeleteUserDialog"
:on-cancel="deleteUserDialog.bind(this, false)" :on-cancel="deleteUserDialog.bind(this, false)"
@ -156,7 +156,7 @@
</button> </button>
</template> </template>
</DialogModal> </DialogModal>
</teleport> </portal>
</div> </div>
</template> </template>

View file

@ -4,6 +4,7 @@ import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue' 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 RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
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'
@ -46,6 +47,7 @@ const Notification = {
UserCard, UserCard,
Timeago, Timeago,
Status, Status,
Report,
RichContent RichContent
}, },
methods: { methods: {

View file

@ -33,7 +33,7 @@
> >
<a <a
class="avatar-container" class="avatar-container"
:href="$router.resolve(userProfileLink).href" :href="notification.from_profile.statusnet_profile_url"
@click.stop.prevent.capture="toggleUserExpanded" @click.stop.prevent.capture="toggleUserExpanded"
> >
<UserAvatar <UserAvatar
@ -65,16 +65,12 @@
v-else v-else
class="username" class="username"
:title="'@'+notification.from_profile.screen_name_ui" :title="'@'+notification.from_profile.screen_name_ui"
> >{{ notification.from_profile.name }}</span>
{{ notification.from_profile.name }}
</span>
{{ ' ' }}
<span v-if="notification.type === 'like'"> <span v-if="notification.type === 'like'">
<FAIcon <FAIcon
class="type-icon" class="type-icon"
icon="star" icon="star"
/> />
{{ ' ' }}
<small>{{ $t('notifications.favorited_you') }}</small> <small>{{ $t('notifications.favorited_you') }}</small>
</span> </span>
<span v-if="notification.type === 'repeat'"> <span v-if="notification.type === 'repeat'">
@ -83,7 +79,6 @@
icon="retweet" icon="retweet"
:title="$t('tool_tip.repeat')" :title="$t('tool_tip.repeat')"
/> />
{{ ' ' }}
<small>{{ $t('notifications.repeated_you') }}</small> <small>{{ $t('notifications.repeated_you') }}</small>
</span> </span>
<span v-if="notification.type === 'follow'"> <span v-if="notification.type === 'follow'">
@ -91,7 +86,6 @@
class="type-icon" class="type-icon"
icon="user-plus" icon="user-plus"
/> />
{{ ' ' }}
<small>{{ $t('notifications.followed_you') }}</small> <small>{{ $t('notifications.followed_you') }}</small>
</span> </span>
<span v-if="notification.type === 'follow_request'"> <span v-if="notification.type === 'follow_request'">
@ -99,7 +93,6 @@
class="type-icon" class="type-icon"
icon="user" icon="user"
/> />
{{ ' ' }}
<small>{{ $t('notifications.follow_request') }}</small> <small>{{ $t('notifications.follow_request') }}</small>
</span> </span>
<span v-if="notification.type === 'move'"> <span v-if="notification.type === 'move'">
@ -107,19 +100,18 @@
class="type-icon" class="type-icon"
icon="suitcase-rolling" icon="suitcase-rolling"
/> />
{{ ' ' }}
<small>{{ $t('notifications.migrated_to') }}</small> <small>{{ $t('notifications.migrated_to') }}</small>
</span> </span>
<span v-if="notification.type === 'pleroma:emoji_reaction'"> <span v-if="notification.type === 'pleroma:emoji_reaction'">
<small> <small>
<i18n-t <i18n path="notifications.reacted_with">
scope="global"
keypath="notifications.reacted_with"
>
<span class="emoji-reaction-emoji">{{ notification.emoji }}</span> <span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
</i18n-t> </i18n>
</small> </small>
</span> </span>
<span v-if="notification.type === 'pleroma:report'">
<small>{{ $t('notifications.submitted_report') }}</small>
</span>
</div> </div>
<div <div
v-if="isStatusNotification" v-if="isStatusNotification"
@ -172,26 +164,18 @@
v-if="notification.type === 'follow_request'" v-if="notification.type === 'follow_request'"
style="white-space: nowrap;" style="white-space: nowrap;"
> >
<button <FAIcon
class="button-unstyled" icon="check"
class="fa-scale-110 fa-old-padding follow-request-accept"
:title="$t('tool_tip.accept_follow_request')" :title="$t('tool_tip.accept_follow_request')"
@click="approveUser()" @click="approveUser()"
> />
<FAIcon <FAIcon
icon="check" icon="times"
class="fa-scale-110 fa-old-padding follow-request-accept" class="fa-scale-110 fa-old-padding follow-request-reject"
/>
</button>
<button
class="button-unstyled"
:title="$t('tool_tip.reject_follow_request')" :title="$t('tool_tip.reject_follow_request')"
@click="denyUser()" @click="denyUser()"
> />
<FAIcon
icon="times"
class="fa-scale-110 fa-old-padding follow-request-reject"
/>
</button>
</div> </div>
</div> </div>
<div <div
@ -202,6 +186,10 @@
@{{ notification.target.screen_name_ui }} @{{ notification.target.screen_name_ui }}
</router-link> </router-link>
</div> </div>
<Report
v-else-if="notification.type === 'pleroma:report'"
:report-id="notification.report.id"
/>
<template v-else> <template v-else>
<StatusContent <StatusContent
class="faint" class="faint"

View file

@ -59,11 +59,15 @@
height: 32px; height: 32px;
} }
--link: var(--faintLink); .faint {
--text: var(--faint); --link: var(--faintLink);
--text: var(--faint);
}
} }
.follow-request-accept { .follow-request-accept {
cursor: pointer;
&:hover { &:hover {
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
@ -71,6 +75,8 @@
} }
.follow-request-reject { .follow-request-reject {
cursor: pointer;
&:hover { &:hover {
color: $fallback--cRed; color: $fallback--cRed;
color: var(--cRed, $fallback--cRed); color: var(--cRed, $fallback--cRed);

View file

@ -11,21 +11,21 @@
</label> </label>
<Checkbox <Checkbox
v-if="typeof fallback !== 'undefined'" v-if="typeof fallback !== 'undefined'"
:model-value="present" :checked="present"
:disabled="disabled" :disabled="disabled"
class="opt" class="opt"
@update:modelValue="$emit('update:modelValue', !present ? fallback : undefined)" @change="$emit('input', !present ? fallback : undefined)"
/> />
<input <input
:id="name" :id="name"
class="input-number" class="input-number"
type="number" type="number"
:value="modelValue || fallback" :value="value || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
max="1" max="1"
min="0" min="0"
step=".05" step=".05"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('input', $event.target.value)"
> >
</div> </div>
</template> </template>
@ -37,12 +37,11 @@ export default {
Checkbox Checkbox
}, },
props: [ props: [
'name', 'modelValue', 'fallback', 'disabled' 'name', 'value', 'fallback', 'disabled'
], ],
emits: ['update:modelValue'],
computed: { computed: {
present () { present () {
return typeof this.modelValue !== 'undefined' return typeof this.value !== 'undefined'
} }
} }
} }

View file

@ -21,7 +21,7 @@ export default {
} }
this.$store.dispatch('trackPoll', this.pollId) this.$store.dispatch('trackPoll', this.pollId)
}, },
unmounted () { destroyed () {
this.$store.dispatch('untrackPoll', this.pollId) this.$store.dispatch('untrackPoll', this.pollId)
}, },
computed: { computed: {

View file

@ -71,18 +71,13 @@
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp; {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp;
</template> </template>
</div> </div>
<span> <i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
<i18n-t <Timeago
scope="global" :time="expiresAt"
:keypath="expired ? 'polls.expired' : 'polls.expires_in'" :auto-update="60"
> :now-threshold="0"
<Timeago />
:time="expiresAt" </i18n>
:auto-update="60"
:now-threshold="0"
/>
</i18n-t>
</span>
</div> </div>
</div> </div>
</template> </template>

View file

@ -72,7 +72,6 @@
:max="maxExpirationInCurrentUnit" :max="maxExpirationInCurrentUnit"
@change="expiryAmountChange" @change="expiryAmountChange"
> >
{{ ' ' }}
<Select <Select
v-model="expiryUnit" v-model="expiryUnit"
unstyled="true" unstyled="true"

View file

@ -178,7 +178,7 @@ const Popover = {
created () { created () {
document.addEventListener('click', this.onClickOutside) document.addEventListener('click', this.onClickOutside)
}, },
unmounted () { destroyed () {
document.removeEventListener('click', this.onClickOutside) document.removeEventListener('click', this.onClickOutside)
this.hidePopover() this.hidePopover()
} }

View file

@ -149,30 +149,5 @@
} }
} }
.button-default.dropdown-item {
&,
i[class*=icon-] {
color: $fallback--text;
color: var(--btnText, $fallback--text);
}
&:active {
background-color: $fallback--lightBg;
background-color: var(--selectedMenuPopover, $fallback--lightBg);
color: $fallback--link;
color: var(--selectedMenuPopoverText, $fallback--link);
}
&:disabled {
color: $fallback--text;
color: var(--btnDisabledText, $fallback--text);
}
&.toggled {
color: $fallback--text;
color: var(--btnToggledText, $fallback--text);
}
}
} }
</style> </style>

View file

@ -78,12 +78,6 @@ const PostStatusForm = {
'emojiPickerPlacement', 'emojiPickerPlacement',
'optimisticPosting' 'optimisticPosting'
], ],
emits: [
'posted',
'resize',
'mediaplay',
'mediapause'
],
components: { components: {
MediaUpload, MediaUpload,
EmojiInput, EmojiInput,

View file

@ -18,12 +18,11 @@
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" /> <FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
</div> </div>
<div class="form-group"> <div class="form-group">
<i18n-t <i18n
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning" v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
keypath="post_status.account_not_locked_warning" path="post_status.account_not_locked_warning"
tag="p" tag="p"
class="visibility-notice" class="visibility-notice"
scope="global"
> >
<button <button
class="button-unstyled -link" class="button-unstyled -link"
@ -31,7 +30,7 @@
> >
{{ $t('post_status.account_not_locked_warning_link') }} {{ $t('post_status.account_not_locked_warning_link') }}
</button> </button>
</i18n-t> </i18n>
<p <p
v-if="!hideScopeNotice && newStatus.visibility === 'public'" v-if="!hideScopeNotice && newStatus.visibility === 'public'"
class="visibility-notice notice-dismissible" class="visibility-notice notice-dismissible"
@ -282,15 +281,11 @@
class="alert error" class="alert error"
> >
Error: {{ error }} Error: {{ error }}
<button <FAIcon
class="button-unstyled" class="fa-scale-110 fa-old-padding"
icon="times"
@click="clearError" @click="clearError"
> />
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
/>
</button>
</div> </div>
<gallery <gallery
v-if="newStatus.files && newStatus.files.length > 0" v-if="newStatus.files && newStatus.files.length > 0"

View file

@ -9,7 +9,7 @@ const PublicAndExternalTimeline = {
created () { created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' }) this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
}, },
unmounted () { destroyed () {
this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal') this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
} }
} }

View file

@ -9,7 +9,7 @@ const PublicTimeline = {
created () { created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' }) this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
}, },
unmounted () { destroyed () {
this.$store.dispatch('stopFetchingTimeline', 'public') this.$store.dispatch('stopFetchingTimeline', 'public')
} }

View file

@ -15,7 +15,7 @@
class="opt" class="opt"
type="checkbox" type="checkbox"
:checked="present" :checked="present"
@change="$emit('update:modelValue', !present ? fallback : undefined)" @input="$emit('input', !present ? fallback : undefined)"
> >
<label <label
v-if="typeof fallback !== 'undefined'" v-if="typeof fallback !== 'undefined'"
@ -26,23 +26,23 @@
:id="name" :id="name"
class="input-number" class="input-number"
type="range" type="range"
:value="modelValue || fallback" :value="value || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
:max="max || hardMax || 100" :max="max || hardMax || 100"
:min="min || hardMin || 0" :min="min || hardMin || 0"
:step="step || 1" :step="step || 1"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('input', $event.target.value)"
> >
<input <input
:id="name" :id="name"
class="input-number" class="input-number"
type="number" type="number"
:value="modelValue || fallback" :value="value || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
:max="hardMax" :max="hardMax"
:min="hardMin" :min="hardMin"
:step="step || 1" :step="step || 1"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('input', $event.target.value)"
> >
</div> </div>
</template> </template>
@ -50,12 +50,11 @@
<script> <script>
export default { export default {
props: [ props: [
'name', 'modelValue', 'fallback', 'disabled', 'label', 'max', 'min', 'step', 'hardMin', 'hardMax' 'name', 'value', 'fallback', 'disabled', 'label', 'max', 'min', 'step', 'hardMin', 'hardMax'
], ],
emits: ['update:modelValue'],
computed: { computed: {
present () { present () {
return typeof this.modelValue !== 'undefined' return typeof this.value !== 'undefined'
} }
} }
} }

View file

@ -1,9 +1,9 @@
import useVuelidate from '@vuelidate/core' import { validationMixin } from 'vuelidate'
import { required, requiredIf, sameAs } from '@vuelidate/validators' import { required, requiredIf, sameAs } from 'vuelidate/lib/validators'
import { mapActions, mapState } from 'vuex' import { mapActions, mapState } from 'vuex'
const registration = { const registration = {
setup () { return { v$: useVuelidate() } }, mixins: [validationMixin],
data: () => ({ data: () => ({
user: { user: {
email: '', email: '',
@ -24,7 +24,7 @@ const registration = {
password: { required }, password: { required },
confirm: { confirm: {
required, required,
sameAs: sameAs(this.user.password) sameAsPassword: sameAs('password')
}, },
reason: { required: requiredIf(() => this.accountApprovalRequired) } reason: { required: requiredIf(() => this.accountApprovalRequired) }
} }
@ -65,9 +65,9 @@ const registration = {
this.user.captcha_token = this.captcha.token this.user.captcha_token = this.captcha.token
this.user.captcha_answer_data = this.captcha.answer_data this.user.captcha_answer_data = this.captcha.answer_data
this.v$.$touch() this.$v.$touch()
if (!this.v$.$invalid) { if (!this.$v.$invalid) {
try { try {
await this.signUp(this.user) await this.signUp(this.user)
this.$router.push({ name: 'friends' }) this.$router.push({ name: 'friends' })

View file

@ -12,7 +12,7 @@
<div class="text-fields"> <div class="text-fields">
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': v$.user.username.$error }" :class="{ 'form-group--error': $v.user.username.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -20,18 +20,18 @@
>{{ $t('login.username') }}</label> >{{ $t('login.username') }}</label>
<input <input
id="sign-up-username" id="sign-up-username"
v-model.trim="v$.user.username.$model" v-model.trim="$v.user.username.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
:placeholder="$t('registration.username_placeholder')" :placeholder="$t('registration.username_placeholder')"
> >
</div> </div>
<div <div
v-if="v$.user.username.$dirty" v-if="$v.user.username.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!v$.user.username.required"> <li v-if="!$v.user.username.required">
<span>{{ $t('registration.validations.username_required') }}</span> <span>{{ $t('registration.validations.username_required') }}</span>
</li> </li>
</ul> </ul>
@ -39,7 +39,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': v$.user.fullname.$error }" :class="{ 'form-group--error': $v.user.fullname.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -47,18 +47,18 @@
>{{ $t('registration.fullname') }}</label> >{{ $t('registration.fullname') }}</label>
<input <input
id="sign-up-fullname" id="sign-up-fullname"
v-model.trim="v$.user.fullname.$model" v-model.trim="$v.user.fullname.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
:placeholder="$t('registration.fullname_placeholder')" :placeholder="$t('registration.fullname_placeholder')"
> >
</div> </div>
<div <div
v-if="v$.user.fullname.$dirty" v-if="$v.user.fullname.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!v$.user.fullname.required"> <li v-if="!$v.user.fullname.required">
<span>{{ $t('registration.validations.fullname_required') }}</span> <span>{{ $t('registration.validations.fullname_required') }}</span>
</li> </li>
</ul> </ul>
@ -66,7 +66,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': v$.user.email.$error }" :class="{ 'form-group--error': $v.user.email.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -74,18 +74,18 @@
>{{ $t('registration.email') }}</label> >{{ $t('registration.email') }}</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"
> >
</div> </div>
<div <div
v-if="v$.user.email.$dirty" v-if="$v.user.email.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!v$.user.email.required"> <li v-if="!$v.user.email.required">
<span>{{ $t('registration.validations.email_required') }}</span> <span>{{ $t('registration.validations.email_required') }}</span>
</li> </li>
</ul> </ul>
@ -107,7 +107,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': v$.user.password.$error }" :class="{ 'form-group--error': $v.user.password.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -122,11 +122,11 @@
> >
</div> </div>
<div <div
v-if="v$.user.password.$dirty" v-if="$v.user.password.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!v$.user.password.required"> <li v-if="!$v.user.password.required">
<span>{{ $t('registration.validations.password_required') }}</span> <span>{{ $t('registration.validations.password_required') }}</span>
</li> </li>
</ul> </ul>
@ -134,7 +134,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': v$.user.confirm.$error }" :class="{ 'form-group--error': $v.user.confirm.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -149,14 +149,14 @@
> >
</div> </div>
<div <div
v-if="v$.user.confirm.$dirty" v-if="$v.user.confirm.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!v$.user.confirm.required"> <li v-if="!$v.user.confirm.required">
<span>{{ $t('registration.validations.password_confirmation_required') }}</span> <span>{{ $t('registration.validations.password_confirmation_required') }}</span>
</li> </li>
<li v-if="!v$.user.confirm.sameAsPassword"> <li v-if="!$v.user.confirm.sameAsPassword">
<span>{{ $t('registration.validations.password_confirmation_match') }}</span> <span>{{ $t('registration.validations.password_confirmation_match') }}</span>
</li> </li>
</ul> </ul>

View file

@ -0,0 +1,34 @@
import Select from '../select/select.vue'
import StatusContent from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const Report = {
props: [
'reportId'
],
components: {
Select,
StatusContent,
Timeago
},
computed: {
report () {
return this.$store.state.reports.reports[this.reportId] || {}
},
state: {
get: function () { return this.report.state },
set: function (val) { this.setReportState(val) }
}
},
methods: {
generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
setReportState (state) {
return this.$store.dispatch('setReportState', { id: this.report.id, state })
}
}
}
export default Report

View file

@ -0,0 +1,43 @@
@import '../../_variables.scss';
.Report {
.report-content {
margin: 0.5em 0 1em;
}
.report-state {
margin: 0.5em 0 1em;
}
.reported-status {
border: 1px solid $fallback--faint;
border-color: var(--faint, $fallback--faint);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
color: $fallback--text;
color: var(--text, $fallback--text);
display: block;
padding: 0.5em;
margin: 0.5em 0;
.status-content {
pointer-events: none;
}
.reported-status-heading {
display: flex;
width: 100%;
justify-content: space-between;
margin-bottom: 0.2em;
}
.reported-status-name {
font-weight: bold;
}
}
.note {
width: 100%;
margin-bottom: 0.5em;
}
}

View file

@ -0,0 +1,74 @@
<template>
<div class="Report">
<div class="reported-user">
<span>{{ $t('report.reported_user') }}</span>
<router-link :to="generateUserProfileLink(report.acct)">
@{{ report.acct.screen_name }}
</router-link>
</div>
<div class="reporter">
<span>{{ $t('report.reporter') }}</span>
<router-link :to="generateUserProfileLink(report.actor)">
@{{ report.actor.screen_name }}
</router-link>
</div>
<div class="report-state">
<span>{{ $t('report.state') }}</span>
<Select
:id="report-state"
v-model="state"
class="form-control"
>
<option
v-for="state in ['open', 'closed', 'resolved']"
:key="state"
:value="state"
>
{{ $t('report.state_' + state) }}
</option>
</Select>
</div>
<RichContent
class="report-content"
:html="report.content"
:emoji="[]"
/>
<div v-if="report.statuses.length">
<small>{{ $t('report.reported_statuses') }}</small>
<router-link
v-for="status in report.statuses"
:key="status.id"
:to="{ name: 'conversation', params: { id: status.id } }"
class="reported-status"
>
<div class="reported-status-heading">
<span class="reported-status-name">{{ status.user.name }}</span>
<Timeago
:time="status.created_at"
:auto-update="240"
class="faint"
/>
</div>
<status-content :status="status" />
</router-link>
</div>
<div v-if="report.notes.length">
<small>{{ $t('report.notes') }}</small>
<div
v-for="note in report.notes"
:key="note.id"
class="note"
>
<span>{{ note.content }}</span>
<Timeago
:time="note.created_at"
:auto-update="240"
class="faint"
/>
</div>
</div>
</div>
</template>
<script src="./report.js"></script>
<style src="./report.scss" lang="scss"></style>

View file

@ -1,3 +1,4 @@
import Vue from 'vue'
import { unescape, flattenDeep } from 'lodash' import { unescape, flattenDeep } from 'lodash'
import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js' import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js' import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
@ -26,12 +27,8 @@ import './rich_content.scss'
* *
* Apart from that one small hiccup with emit in render this _should_ be vue3-ready * Apart from that one small hiccup with emit in render this _should_ be vue3-ready
*/ */
export default { export default Vue.component('RichContent', {
name: 'RichContent', name: 'RichContent',
components: {
MentionsLine,
HashtagLink
},
props: { props: {
// Original html content // Original html content
html: { html: {
@ -61,7 +58,7 @@ export default {
} }
}, },
// NEVER EVER TOUCH DATA INSIDE RENDER // NEVER EVER TOUCH DATA INSIDE RENDER
render () { render (h) {
// Pre-process HTML // Pre-process HTML
const { newHtml: html } = preProcessPerLine(this.html, this.greentext) const { newHtml: html } = preProcessPerLine(this.html, this.greentext)
let currentMentions = null // Current chain of mentions, we group all mentions together let currentMentions = null // Current chain of mentions, we group all mentions together
@ -79,19 +76,18 @@ export default {
const renderImage = (tag) => { const renderImage = (tag) => {
return <StillImage return <StillImage
{...getAttrs(tag)} {...{ attrs: getAttrs(tag) }}
class="img" class="img"
/> />
} }
const renderHashtag = (attrs, children, encounteredTextReverse) => { const renderHashtag = (attrs, children, encounteredTextReverse) => {
const { index, ...linkData } = getLinkData(attrs, children, tagsIndex++) const linkData = getLinkData(attrs, children, tagsIndex++)
writtenTags.push(linkData) writtenTags.push(linkData)
if (!encounteredTextReverse) { if (!encounteredTextReverse) {
lastTags.push(linkData) lastTags.push(linkData)
} }
const { url, tag, content } = linkData return <HashtagLink {...{ props: linkData }}/>
return <HashtagLink url={url} tag={tag} content={content}/>
} }
const renderMention = (attrs, children) => { const renderMention = (attrs, children) => {
@ -226,7 +222,7 @@ export default {
attrs.target = '_blank' attrs.target = '_blank'
const newChildren = [...children].reverse().map(processItemReverse).reverse() const newChildren = [...children].reverse().map(processItemReverse).reverse()
return <a {...attrs}> return <a {...{ attrs }}>
{ newChildren } { newChildren }
</a> </a>
} }
@ -239,7 +235,7 @@ export default {
const newChildren = Array.isArray(children) const newChildren = Array.isArray(children)
? [...children].reverse().map(processItemReverse).reverse() ? [...children].reverse().map(processItemReverse).reverse()
: children : children
return <Tag {...getAttrs(opener)}> return <Tag {...{ attrs: getAttrs(opener) }}>
{ newChildren } { newChildren }
</Tag> </Tag>
} else { } else {
@ -270,7 +266,7 @@ export default {
return result return result
} }
} })
const getLinkData = (attrs, children, index) => { const getLinkData = (attrs, children, index) => {
const stripTags = (item) => { const stripTags = (item) => {

View file

@ -16,7 +16,6 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</button> </button>
{{ ' ' }}
<button <button
v-if="showPrivate" v-if="showPrivate"
class="button-unstyled scope" class="button-unstyled scope"
@ -30,7 +29,6 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</button> </button>
{{ ' ' }}
<button <button
v-if="showUnlisted" v-if="showUnlisted"
class="button-unstyled scope" class="button-unstyled scope"
@ -44,7 +42,6 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</button> </button>
{{ ' ' }}
<button <button
v-if="showPublic" v-if="showPublic"
class="button-unstyled scope" class="button-unstyled scope"

View file

@ -1,7 +1,6 @@
import FollowCard from '../follow_card/follow_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import Conversation from '../conversation/conversation.vue' import Conversation from '../conversation/conversation.vue'
import Status from '../status/status.vue' import Status from '../status/status.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import map from 'lodash/map' import map from 'lodash/map'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -18,8 +17,7 @@ const Search = {
components: { components: {
FollowCard, FollowCard,
Conversation, Conversation,
Status, Status
TabSwitcher
}, },
props: [ props: [
'query' 'query'

View file

@ -8,9 +8,12 @@ library.add(
) )
export default { export default {
emits: ['update:modelValue'], model: {
prop: 'value',
event: 'change'
},
props: [ props: [
'modelValue', 'value',
'disabled', 'disabled',
'unstyled', 'unstyled',
'kind' 'kind'

View file

@ -1,3 +1,4 @@
<template> <template>
<label <label
class="Select input" class="Select input"
@ -5,12 +6,11 @@
> >
<select <select
:disabled="disabled" :disabled="disabled"
:value="modelValue" :value="value"
@change="$emit('update:modelValue', $event.target.value)" @change="$emit('change', $event.target.value)"
> >
<slot /> <slot />
</select> </select>
{{ ' ' }}
<FAIcon <FAIcon
class="select-down-icon" class="select-down-icon"
icon="chevron-down" icon="chevron-down"
@ -23,8 +23,7 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
/* TODO fix order of styles */ .Select {
label.Select {
padding: 0; padding: 0;
select { select {

View file

@ -6,9 +6,9 @@
> >
<div class="selectable-list-checkbox-wrapper"> <div class="selectable-list-checkbox-wrapper">
<Checkbox <Checkbox
:model-value="allSelected" :checked="allSelected"
:indeterminate="someSelected" :indeterminate="someSelected"
@update:model-value="toggleAll" @change="toggleAll"
> >
{{ $t('selectable_list.select_all') }} {{ $t('selectable_list.select_all') }}
</Checkbox> </Checkbox>
@ -31,8 +31,8 @@
> >
<div class="selectable-list-checkbox-wrapper"> <div class="selectable-list-checkbox-wrapper">
<Checkbox <Checkbox
:model-value="isSelected(item)" :checked="isSelected(item)"
@update:model-value="checked => toggle(checked, item)" @change="checked => toggle(checked, item)"
/> />
</div> </div>
<slot <slot

View file

@ -4,9 +4,9 @@
class="BooleanSetting" class="BooleanSetting"
> >
<Checkbox <Checkbox
:model-value="state" :checked="state"
:disabled="disabled" :disabled="disabled"
@update:modelValue="update" @change="update"
> >
<span <span
v-if="!!$slots.default" v-if="!!$slots.default"
@ -14,7 +14,6 @@
> >
<slot /> <slot />
</span> </span>
{{ ' ' }}
<ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox> <ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
</label> </label>
</template> </template>

View file

@ -4,11 +4,10 @@
class="ChoiceSetting" class="ChoiceSetting"
> >
<slot /> <slot />
{{ ' ' }}
<Select <Select
:model-value="state" :value="state"
:disabled="disabled" :disabled="disabled"
@update:modelValue="update" @change="update"
> >
<option <option
v-for="option in options" v-for="option in options"

View file

@ -8,7 +8,7 @@ export default {
path: String, path: String,
disabled: Boolean, disabled: Boolean,
min: Number, min: Number,
expert: [Number, String] expert: Number
}, },
computed: { computed: {
pathDefault () { pathDefault () {

View file

@ -16,7 +16,6 @@
:value="state" :value="state"
@change="update" @change="update"
> >
{{ ' ' }}
<ModifiedIndicator :changed="isChanged" /> <ModifiedIndicator :changed="isChanged" />
</span> </span>
</template> </template>

View file

@ -56,8 +56,8 @@ const SettingsModal = {
SettingsModalContent: getResettableAsyncComponent( SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'), () => import('./settings_modal_content.vue'),
{ {
loadingComponent: PanelLoading, loading: PanelLoading,
errorComponent: AsyncComponentError, error: AsyncComponentError,
delay: 0 delay: 0
} }
) )

View file

@ -54,10 +54,5 @@
>* { >* {
margin-right: 0.5em; margin-right: 0.5em;
} }
.extra-content {
display: flex;
flex-grow: 1;
}
} }
} }

View file

@ -11,7 +11,7 @@
{{ $t('settings.settings') }} {{ $t('settings.settings') }}
</span> </span>
<transition name="fade"> <transition name="fade">
<div v-if="currentSaveStateNotice"> <template v-if="currentSaveStateNotice">
<div <div
v-if="currentSaveStateNotice.error" v-if="currentSaveStateNotice.error"
class="alert error" class="alert error"
@ -27,7 +27,7 @@
> >
{{ $t('settings.saving_ok') }} {{ $t('settings.saving_ok') }}
</div> </div>
</div> </template>
</transition> </transition>
<button <button
class="btn button-default" class="btn button-default"
@ -68,7 +68,6 @@
:title="$t('general.close')" :title="$t('general.close')"
> >
<span>{{ $t("settings.file_export_import.backup_restore") }}</span> <span>{{ $t("settings.file_export_import.backup_restore") }}</span>
{{ ' ' }}
<FAIcon <FAIcon
icon="chevron-down" icon="chevron-down"
/> />
@ -110,16 +109,9 @@
</template> </template>
</Popover> </Popover>
<Checkbox <Checkbox v-model="expertLevel">
:model-value="!!expertLevel"
@update:modelValue="expertLevel = Number($event)"
>
{{ $t("settings.expert_mode") }} {{ $t("settings.expert_mode") }}
</Checkbox> </Checkbox>
<span
id="unscrolled-content"
class="extra-content"
/>
</div> </div>
</div> </div>
</Modal> </Modal>

View file

@ -1,4 +1,4 @@
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import DataImportExportTab from './tabs/data_import_export_tab.vue' import DataImportExportTab from './tabs/data_import_export_tab.vue'
import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue' import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
@ -53,9 +53,6 @@ const SettingsModalContent = {
}, },
open () { open () {
return this.$store.state.interface.settingsModalState !== 'hidden' return this.$store.state.interface.settingsModalState !== 'hidden'
},
bodyLock () {
return this.$store.state.interface.settingsModalState === 'visible'
} }
}, },
methods: { methods: {
@ -63,8 +60,8 @@ const SettingsModalContent = {
const targetTab = this.$store.state.interface.settingsModalTargetTab const targetTab = this.$store.state.interface.settingsModalTargetTab
// We're being told to open in specific tab // We're being told to open in specific tab
if (targetTab) { if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => { const tabIndex = this.$refs.tabSwitcher.$slots.default.findIndex(elm => {
return elm.props && elm.props['data-tab-name'] === targetTab return elm.data && elm.data.attrs['data-tab-name'] === targetTab
}) })
if (tabIndex >= 0) { if (tabIndex >= 0) {
this.$refs.tabSwitcher.setTab(tabIndex) this.$refs.tabSwitcher.setTab(tabIndex)

View file

@ -4,7 +4,6 @@
class="settings_tab-switcher" class="settings_tab-switcher"
:side-tab-bar="true" :side-tab-bar="true"
:scrollable-tabs="true" :scrollable-tabs="true"
:body-scroll-lock="bodyLock"
> >
<div <div
:label="$t('settings.general')" :label="$t('settings.general')"

View file

@ -72,10 +72,22 @@
<div>{{ $t('settings.filtering_explanation') }}</div> <div>{{ $t('settings.filtering_explanation') }}</div>
</li> </li>
<h3>{{ $t('settings.attachments') }}</h3> <h3>{{ $t('settings.attachments') }}</h3>
<li v-if="expertLevel > 0">
<label for="maxThumbnails">
{{ $t('settings.max_thumbnails') }}
</label>
<input
id="maxThumbnails"
path.number="maxThumbnails"
class="number-input"
type="number"
min="0"
step="1"
>
</li>
<li> <li>
<IntegerSetting <IntegerSetting
path="maxThumbnails" path="maxThumbnails"
expert="1"
:min="0" :min="0"
> >
{{ $t('settings.max_thumbnails') }} {{ $t('settings.max_thumbnails') }}

View file

@ -2,7 +2,7 @@ import get from 'lodash/get'
import map from 'lodash/map' import map from 'lodash/map'
import reject from 'lodash/reject' import reject from 'lodash/reject'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import BlockCard from 'src/components/block_card/block_card.vue' import BlockCard from 'src/components/block_card/block_card.vue'
import MuteCard from 'src/components/mute_card/mute_card.vue' import MuteCard from 'src/components/mute_card/mute_card.vue'
import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue' import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue'

View file

@ -54,20 +54,16 @@
border-radius: var(--tooltipRadius, $fallback--tooltipRadius); border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
opacity: 0.7; opacity: 0.7;
color: white;
width: 1.5em; width: 1.5em;
height: 1.5em; height: 1.5em;
text-align: center; text-align: center;
line-height: 1.5em; line-height: 1.5em;
font-size: 1.5em; font-size: 1.5em;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
opacity: 1; opacity: 1;
} }
svg {
color: white;
}
} }
.oauth-tokens { .oauth-tokens {

View file

@ -68,9 +68,8 @@
class="delete-field button-unstyled -hover-highlight" class="delete-field button-unstyled -hover-highlight"
@click="deleteField(i)" @click="deleteField(i)"
> >
<!-- TODO something is wrong with v-show here -->
<FAIcon <FAIcon
v-if="newFields.length > 1" v-show="newFields.length > 1"
icon="times" icon="times"
/> />
</button> </button>
@ -107,17 +106,14 @@
:src="user.profile_image_url_original" :src="user.profile_image_url_original"
class="current-avatar" class="current-avatar"
> >
<button <FAIcon
v-if="!isDefaultAvatar && pickAvatarBtnVisible" v-if="!isDefaultAvatar && pickAvatarBtnVisible"
:title="$t('settings.reset_avatar')" :title="$t('settings.reset_avatar')"
class="reset-button"
icon="times"
type="button"
@click="resetAvatar" @click="resetAvatar"
class="button-unstyled reset-button" />
>
<FAIcon
icon="times"
type="button"
/>
</button>
</div> </div>
<p>{{ $t('settings.set_new_avatar') }}</p> <p>{{ $t('settings.set_new_avatar') }}</p>
<button <button
@ -139,17 +135,14 @@
<h2>{{ $t('settings.profile_banner') }}</h2> <h2>{{ $t('settings.profile_banner') }}</h2>
<div class="banner-background-preview"> <div class="banner-background-preview">
<img :src="user.cover_photo"> <img :src="user.cover_photo">
<button <FAIcon
v-if="!isDefaultBanner" v-if="!isDefaultBanner"
class="button-unstyled reset-button"
:title="$t('settings.reset_profile_banner')" :title="$t('settings.reset_profile_banner')"
class="reset-button"
icon="times"
type="button"
@click="resetBanner" @click="resetBanner"
> />
<FAIcon
icon="times"
type="button"
/>
</button>
</div> </div>
<p>{{ $t('settings.set_new_profile_banner') }}</p> <p>{{ $t('settings.set_new_profile_banner') }}</p>
<img <img
@ -181,17 +174,14 @@
<h2>{{ $t('settings.profile_background') }}</h2> <h2>{{ $t('settings.profile_background') }}</h2>
<div class="banner-background-preview"> <div class="banner-background-preview">
<img :src="user.background_image"> <img :src="user.background_image">
<button <FAIcon
v-if="!isDefaultBackground" v-if="!isDefaultBackground"
class="button-unstyled reset-button"
:title="$t('settings.reset_profile_background')" :title="$t('settings.reset_profile_background')"
class="reset-button"
icon="times"
type="button"
@click="resetBackground" @click="resetBackground"
> />
<FAIcon
icon="times"
type="button"
/>
</button>
</div> </div>
<p>{{ $t('settings.set_new_profile_background') }}</p> <p>{{ $t('settings.set_new_profile_background') }}</p>
<img <img

View file

@ -29,14 +29,14 @@
{{ $t('settings.style.preview.content') }} {{ $t('settings.style.preview.content') }}
</h4> </h4>
<i18n-t scope="global" keypath="settings.style.preview.text"> <i18n path="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>
<a style="color: var(--link)"> <a style="color: var(--link)">
{{ $t('settings.style.preview.link') }} {{ $t('settings.style.preview.link') }}
</a> </a>
</i18n-t> </i18n>
<div class="icons"> <div class="icons">
<FAIcon <FAIcon
@ -72,16 +72,15 @@
:^) :^)
</div> </div>
<div class="content"> <div class="content">
<i18n-t <i18n
keypath="settings.style.preview.fine_print" path="settings.style.preview.fine_print"
tag="span" tag="span"
class="faint" class="faint"
scope="global"
> >
<a style="color: var(--faintLink)"> <a style="color: var(--faintLink)">
{{ $t('settings.style.preview.faint_link') }} {{ $t('settings.style.preview.faint_link') }}
</a> </a>
</i18n-t> </i18n>
</div> </div>
</div> </div>
<div class="separator" /> <div class="separator" />

View file

@ -1,3 +1,4 @@
import { set, delete as del } from 'vue'
import { import {
rgb2hex, rgb2hex,
hex2rgb, hex2rgb,
@ -33,7 +34,7 @@ import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
import ShadowControl from 'src/components/shadow_control/shadow_control.vue' import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
import FontControl from 'src/components/font_control/font_control.vue' import FontControl from 'src/components/font_control/font_control.vue'
import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue' import Select from 'src/components/select/select.vue'
@ -319,9 +320,9 @@ export default {
}, },
set (val) { set (val) {
if (val) { if (val) {
this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _)) set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _)))
} else { } else {
delete this.shadowsLocal[this.shadowSelected] del(this.shadowsLocal, this.shadowSelected)
} }
} }
}, },
@ -333,7 +334,7 @@ export default {
return this.shadowsLocal[this.shadowSelected] return this.shadowsLocal[this.shadowSelected]
}, },
set (v) { set (v) {
this.shadowsLocal[this.shadowSelected] = v set(this.shadowsLocal, this.shadowSelected, v)
} }
}, },
themeValid () { themeValid () {
@ -377,10 +378,6 @@ export default {
// To separate from other random JSON files and possible future source formats // To separate from other random JSON files and possible future source formats
_pleroma_theme_version: 2, theme, source _pleroma_theme_version: 2, theme, source
} }
},
isActive () {
const tabSwitcher = this.$parent
return tabSwitcher ? tabSwitcher.isActive('theme') : false
} }
}, },
components: { components: {
@ -560,7 +557,7 @@ export default {
.filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal')) .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
.filter(_ => !v1OnlyNames.includes(_)) .filter(_ => !v1OnlyNames.includes(_))
.forEach(key => { .forEach(key => {
this.$data[key] = undefined set(this.$data, key, undefined)
}) })
}, },
@ -568,7 +565,7 @@ export default {
Object.keys(this.$data) Object.keys(this.$data)
.filter(_ => _.endsWith('RadiusLocal')) .filter(_ => _.endsWith('RadiusLocal'))
.forEach(key => { .forEach(key => {
this.$data[key] = undefined set(this.$data, key, undefined)
}) })
}, },
@ -576,7 +573,7 @@ export default {
Object.keys(this.$data) Object.keys(this.$data)
.filter(_ => _.endsWith('OpacityLocal')) .filter(_ => _.endsWith('OpacityLocal'))
.forEach(key => { .forEach(key => {
this.$data[key] = undefined set(this.$data, key, undefined)
}) })
}, },

View file

@ -268,6 +268,13 @@
} }
} }
.apply-container {
justify-content: center;
position: absolute;
bottom: 8px;
right: 5px;
}
.radius-item, .radius-item,
.color-item { .color-item {
min-width: 20em; min-width: 20em;
@ -327,25 +334,16 @@
padding: 20px; padding: 20px;
} }
.apply-container {
.btn {
min-height: 28px;
min-width: 10em;
padding: 0 2em;
}
}
.btn { .btn {
margin-left: .25em; margin-left: .25em;
margin-right: .25em; margin-right: .25em;
} }
} }
.extra-content {
.apply-container {
display: flex;
flex-direction: row;
justify-content: space-around;
flex-grow: 1;
.btn {
flex-grow: 1;
min-height: 28px;
min-width: 0;
max-width: 10em;
padding: 0;
}
}
}

View file

@ -903,7 +903,6 @@
<div class="tab-header shadow-selector"> <div class="tab-header shadow-selector">
<div class="select-container"> <div class="select-container">
{{ $t('settings.style.shadows.component') }} {{ $t('settings.style.shadows.component') }}
{{ ' ' }}
<Select <Select
id="shadow-switcher" id="shadow-switcher"
v-model="shadowSelected" v-model="shadowSelected"
@ -925,7 +924,6 @@
> >
{{ $t('settings.style.shadows.override') }} {{ $t('settings.style.shadows.override') }}
</label> </label>
{{ ' ' }}
<input <input
id="override" id="override"
v-model="currentShadowOverriden" v-model="currentShadowOverriden"
@ -951,30 +949,27 @@
:fallback="currentShadowFallback" :fallback="currentShadowFallback"
/> />
<div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'"> <div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
<i18n-t <i18n
scope="global" path="settings.style.shadows.filter_hint.always_drop_shadow"
keypath="settings.style.shadows.filter_hint.always_drop_shadow"
tag="p" tag="p"
> >
<code>filter: drop-shadow()</code> <code>filter: drop-shadow()</code>
</i18n-t> </i18n>
<p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p> <p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
<i18n-t <i18n
scope="global" path="settings.style.shadows.filter_hint.drop_shadow_syntax"
keypath="settings.style.shadows.filter_hint.drop_shadow_syntax"
tag="p" tag="p"
> >
<code>drop-shadow</code> <code>drop-shadow</code>
<code>spread-radius</code> <code>spread-radius</code>
<code>inset</code> <code>inset</code>
</i18n-t> </i18n>
<i18n-t <i18n
scope="global" path="settings.style.shadows.filter_hint.inset_classic"
keypath="settings.style.shadows.filter_hint.inset_classic"
tag="p" tag="p"
> >
<code>box-shadow</code> <code>box-shadow</code>
</i18n-t> </i18n>
<p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p> <p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
</div> </div>
</div> </div>
@ -1021,26 +1016,21 @@
</tab-switcher> </tab-switcher>
</keep-alive> </keep-alive>
<teleport <div class="apply-container">
v-if="isActive" <button
to="#unscrolled-content" class="btn button-default submit"
> :disabled="!themeValid"
<div class="apply-container"> @click="setCustomTheme"
<button >
class="btn button-default submit" {{ $t('general.apply') }}
:disabled="!themeValid" </button>
@click="setCustomTheme" <button
> class="btn button-default"
{{ $t('general.apply') }} @click="clearAll"
</button> >
<button {{ $t('settings.style.switcher.reset') }}
class="btn button-default" </button>
@click="clearAll" </div>
>
{{ $t('settings.style.switcher.reset') }}
</button>
</div>
</teleport>
</div> </div>
</template> </template>

View file

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

View file

@ -30,19 +30,18 @@ const toModel = (object = {}) => ({
}) })
export default { export default {
// 'modelValue' and 'Fallback' can be undefined, but if they are // 'Value' and 'Fallback' can be undefined, but if they are
// initially vue won't detect it when they become something else // initially vue won't detect it when they become something else
// therefore i'm using "ready" which should be passed as true when // therefore i'm using "ready" which should be passed as true when
// data becomes available // data becomes available
props: [ props: [
'modelValue', 'fallback', 'ready' 'value', 'fallback', 'ready'
], ],
emits: ['update:modelValue'],
data () { data () {
return { return {
selectedId: 0, selectedId: 0,
// TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason) // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason)
cValue: (this.modelValue || this.fallback || []).map(toModel) cValue: (this.value || this.fallback || []).map(toModel)
} }
}, },
components: { components: {
@ -71,7 +70,7 @@ export default {
} }
}, },
beforeUpdate () { beforeUpdate () {
this.cValue = this.modelValue || this.fallback this.cValue = this.value || this.fallback
}, },
computed: { computed: {
anyShadows () { anyShadows () {
@ -106,7 +105,7 @@ export default {
!this.usingFallback !this.usingFallback
}, },
usingFallback () { usingFallback () {
return typeof this.modelValue === 'undefined' return typeof this.value === 'undefined'
}, },
rgb () { rgb () {
return hex2rgb(this.selected.color) return hex2rgb(this.selected.color)

View file

@ -204,13 +204,12 @@
v-model="selected.alpha" v-model="selected.alpha"
:disabled="!present" :disabled="!present"
/> />
<i18n-t <i18n
scope="global" path="settings.style.shadows.hintV3"
keypath="settings.style.shadows.hintV3"
tag="p" tag="p"
> >
<code>--variable,mod</code> <code>--variable,mod</code>
</i18n-t> </i18n>
</div> </div>
</div> </div>
</template> </template>

View file

@ -98,7 +98,7 @@
.icon { .icon {
color: $fallback--text; color: $fallback--text;
color: var(--panelText, $fallback--text); color: var(--text, $fallback--text);
margin-right: 0.5em; margin-right: 0.5em;
} }

View file

@ -49,7 +49,7 @@ const SideDrawer = {
currentUser () { currentUser () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
shout () { return this.$store.state.shout.joined }, shout () { return this.$store.state.shout.channel.state === 'joined' },
unseenNotifications () { unseenNotifications () {
return unseenNotificationsFromStore(this.$store) return unseenNotificationsFromStore(this.$store)
}, },

View file

@ -69,7 +69,7 @@ const controlledOrUncontrolledGetters = list => list.reduce((res, name) => {
const controlledName = `controlled${camelized}` const controlledName = `controlled${camelized}`
const uncontrolledName = `uncontrolled${camelized}` const uncontrolledName = `uncontrolled${camelized}`
res[name] = function () { res[name] = function () {
return ((this.$data[toggle] !== undefined || this.$props[toggle] !== undefined) && this[toggle]) ? this[controlledName] : this[uncontrolledName] return this[toggle] ? this[controlledName] : this[uncontrolledName]
} }
return res return res
}, {}) }, {})
@ -225,18 +225,12 @@ const Status = {
muteWordHits () { muteWordHits () {
return muteWordHits(this.status, this.muteWords) return muteWordHits(this.status, this.muteWords)
}, },
rtBotStatus () {
return this.statusoid.user.bot
},
botStatus () { botStatus () {
return this.status.user.bot return this.status.user.bot
}, },
botIndicator () { botIndicator () {
return this.botStatus && !this.hideBotIndication return this.botStatus && !this.hideBotIndication
}, },
rtBotIndicator () {
return this.rtBotStatus && !this.hideBotIndication
},
mentionsLine () { mentionsLine () {
if (!this.headTailLinks) return [] if (!this.headTailLinks) return []
const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url)) const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url))
@ -311,7 +305,7 @@ const Status = {
return this.mergedConfig.hideWordFilteredPosts return this.mergedConfig.hideWordFilteredPosts
}, },
hideStatus () { hideStatus () {
return (!this.shouldNotMute) && ( return (this.virtualHidden || !this.shouldNotMute) && (
(this.muted && this.hideFilteredStatuses) || (this.muted && this.hideFilteredStatuses) ||
(this.userIsMuted && this.hideMutedUsers) || (this.userIsMuted && this.hideMutedUsers) ||
(this.status.thread_muted && this.hideMutedThreads) || (this.status.thread_muted && this.hideMutedThreads) ||
@ -389,9 +383,6 @@ const Status = {
}, },
threadShowing () { threadShowing () {
return this.controlledThreadDisplayStatus === 'showing' return this.controlledThreadDisplayStatus === 'showing'
},
visibilityLocalized () {
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
} }
}, },
methods: { methods: {
@ -481,6 +472,11 @@ const Status = {
'isSuspendable': function (val) { 'isSuspendable': function (val) {
this.suspendable = val this.suspendable = val
} }
},
filters: {
capitalize: function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
} }
} }

View file

@ -406,13 +406,13 @@
margin-left: 20px; margin-left: 20px;
} }
.post-avatar { .avatar:not(.repeater-avatar) {
width: 40px; width: 40px;
height: 40px; height: 40px;
// TODO define those other way somehow? // TODO define those other way somehow?
// stylelint-disable rscss/class-format // stylelint-disable rscss/class-format
&.-compact { &.avatar-compact {
width: 32px; width: 32px;
height: 32px; height: 32px;
} }

View file

@ -1,7 +1,6 @@
<template> <template>
<div <div
v-if="!hideStatus" v-if="!hideStatus"
ref="root"
class="Status" class="Status"
:class="[{ '-focused': isFocused }, { '-conversation': inlineExpanded }]" :class="[{ '-focused': isFocused }, { '-conversation': inlineExpanded }]"
> >
@ -78,7 +77,7 @@
<UserAvatar <UserAvatar
v-if="retweet" v-if="retweet"
class="left-side repeater-avatar" class="left-side repeater-avatar"
:bot="rtBotIndicator" :bot="botIndicator"
:better-shadow="betterShadow" :better-shadow="betterShadow"
:user="statusoid.user" :user="statusoid.user"
/> />
@ -101,7 +100,6 @@
:to="retweeterProfileLink" :to="retweeterProfileLink"
>{{ retweeter }}</router-link> >{{ retweeter }}</router-link>
</span> </span>
{{ ' ' }}
<FAIcon <FAIcon
icon="retweet" icon="retweet"
class="repeat-icon" class="repeat-icon"
@ -122,18 +120,17 @@
v-if="!noHeading" v-if="!noHeading"
class="left-side" class="left-side"
> >
<a <router-link
:href="$router.resolve(userProfileLink).href" :to="userProfileLink"
@click.stop.prevent.capture="toggleUserExpanded" @click.stop.prevent.capture.native="toggleUserExpanded"
> >
<UserAvatar <UserAvatar
class="post-avatar"
:bot="botIndicator" :bot="botIndicator"
:compact="compact" :compact="compact"
:better-shadow="betterShadow" :better-shadow="betterShadow"
:user="status.user" :user="status.user"
/> />
</a> </router-link>
</div> </div>
<div class="right-side"> <div class="right-side">
<UserCard <UserCard
@ -193,7 +190,7 @@
<span <span
v-if="status.visibility" v-if="status.visibility"
class="visibility-icon" class="visibility-icon"
:title="visibilityLocalized" :title="status.visibility | capitalize"
> >
<FAIcon <FAIcon
fixed-width fixed-width
@ -276,7 +273,6 @@
icon="reply" icon="reply"
flip="horizontal" flip="horizontal"
/> />
{{ ' ' }}
<span <span
class="reply-to-text" class="reply-to-text"
> >
@ -296,6 +292,7 @@
:url="replyProfileLink" :url="replyProfileLink"
:user-id="status.in_reply_to_user_id" :user-id="status.in_reply_to_user_id"
:user-screen-name="status.in_reply_to_screen_name" :user-screen-name="status.in_reply_to_screen_name"
:first-mention="false"
/> />
</span> </span>
@ -457,7 +454,6 @@
> >
<div class="left-side"> <div class="left-side">
<UserAvatar <UserAvatar
class="post-avatar"
:compact="compact" :compact="compact"
:bot="botIndicator" :bot="botIndicator"
/> />

View file

@ -15,14 +15,14 @@
:emoji="status.emojis" :emoji="status.emojis"
/> />
<button <button
v-show="longSubject && showingLongSubject" v-if="longSubject && showingLongSubject"
class="button-unstyled -link tall-subject-hider" class="button-unstyled -link tall-subject-hider"
@click.prevent="toggleShowingLongSubject" @click.prevent="toggleShowingLongSubject"
> >
{{ $t("status.hide_full_subject") }} {{ $t("status.hide_full_subject") }}
</button> </button>
<button <button
v-show="longSubject && !showingLongSubject" v-else-if="longSubject"
class="button-unstyled -link tall-subject-hider" class="button-unstyled -link tall-subject-hider"
@click.prevent="toggleShowingLongSubject" @click.prevent="toggleShowingLongSubject"
> >
@ -34,7 +34,7 @@
class="text-wrapper" class="text-wrapper"
> >
<button <button
v-show="hideTallStatus" v-if="hideTallStatus"
class="button-unstyled -link tall-status-hider" class="button-unstyled -link tall-status-hider"
:class="{ '-focused': focused }" :class="{ '-focused': focused }"
@click.prevent="toggleShowMore" @click.prevent="toggleShowMore"
@ -54,7 +54,7 @@
/> />
<button <button
v-show="hideSubjectStatus" v-if="hideSubjectStatus"
class="button-unstyled -link cw-status-hider" class="button-unstyled -link cw-status-hider"
@click.prevent="toggleShowMore" @click.prevent="toggleShowMore"
> >
@ -85,7 +85,7 @@
/> />
</button> </button>
<button <button
v-show="showingMore && !fullContent" v-if="showingMore && !fullContent"
class="button-unstyled -link status-unhider" class="button-unstyled -link status-unhider"
@click.prevent="toggleShowMore" @click.prevent="toggleShowMore"
> >

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