Merge branch 'vue3-again' into shigusegubu-vue3
* vue3-again: (59 commits) cleanup console log fix i18n at places fix all the spacings i could find fix spacings in notifications fix dupe id fix animations cleanup fix capitalization (and localization of tooltips for scope icon) listeners aren't actually used fix selects in settings screen fix tabs not being able to be "disabled" fix avatars not opening inline card fix other weird route fix routes test skip user profile test for now https://github.com/vuejs/test-utils/issues/1382 fix emoji input tests fix richcontent and its tests fix tests running fix mobile post button being too square fix selects ...
This commit is contained in:
commit
f7c2cb95a0
96 changed files with 1375 additions and 1360 deletions
4
.babelrc
4
.babelrc
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env", "@vue/babel-preset-jsx"],
|
"presets": ["@babel/preset-env"],
|
||||||
"plugins": ["@babel/plugin-transform-runtime", "lodash"],
|
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
|
||||||
"comments": false
|
"comments": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:10
|
image: node:12
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- lint
|
- lint
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ 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
|
||||||
|
|
@ -29,12 +30,12 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.vue'],
|
extensions: ['.js', '.jsx', '.vue'],
|
||||||
modules: [
|
modules: [
|
||||||
path.join(__dirname, '../node_modules')
|
path.join(__dirname, '../node_modules')
|
||||||
],
|
],
|
||||||
alias: {
|
alias: {
|
||||||
'vue$': 'vue/dist/vue.runtime.common',
|
'vue': '@vue/compat',
|
||||||
'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'),
|
||||||
|
|
@ -60,7 +61,14 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.vue$/,
|
test: /\.vue$/,
|
||||||
use: 'vue-loader'
|
loader: 'vue-loader',
|
||||||
|
options: {
|
||||||
|
compilerOptions: {
|
||||||
|
compatConfig: {
|
||||||
|
MODE: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.jsx?$/,
|
||||||
|
|
@ -95,6 +103,7 @@ 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: [
|
||||||
|
|
|
||||||
28
package.json
28
package.json
|
|
@ -21,7 +21,7 @@
|
||||||
"@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": "2.0.6",
|
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||||
"body-scroll-lock": "2.7.1",
|
"body-scroll-lock": "2.7.1",
|
||||||
"chromatism": "3.0.0",
|
"chromatism": "3.0.0",
|
||||||
|
|
@ -34,13 +34,14 @@
|
||||||
"portal-vue": "2.1.7",
|
"portal-vue": "2.1.7",
|
||||||
"punycode.js": "2.1.0",
|
"punycode.js": "2.1.0",
|
||||||
"ruffle-mirror": "2021.4.11",
|
"ruffle-mirror": "2021.4.11",
|
||||||
"v-click-outside": "2.1.5",
|
"click-outside-vue3": "4.0.1",
|
||||||
"vue": "2.6.11",
|
"vue": "^3.1.0",
|
||||||
"vue-i18n": "7.8.1",
|
"@vue/compat": "^3.1.0",
|
||||||
"vue-router": "3.0.2",
|
"vue-i18n": "9.1.9",
|
||||||
"vue-template-compiler": "2.6.11",
|
"vue-router": "4.0.14",
|
||||||
"vuelidate": "0.7.7",
|
"@vuelidate/core": "2.0.0-alpha.35",
|
||||||
"vuex": "3.0.1"
|
"@vuelidate/validators": "2.0.0-alpha.27",
|
||||||
|
"vuex": "4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.17.8",
|
"@babel/core": "7.17.8",
|
||||||
|
|
@ -49,8 +50,9 @@
|
||||||
"@babel/register": "7.17.7",
|
"@babel/register": "7.17.7",
|
||||||
"@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-preset-jsx": "1.2.4",
|
"@vue/babel-plugin-jsx": "1.1.1",
|
||||||
"@vue/test-utils": "1.0.0-beta.28",
|
"@vue/test-utils": "2.0.0-rc.17",
|
||||||
|
"@vue/compiler-sfc": "^3.1.0",
|
||||||
"autoprefixer": "6.7.7",
|
"autoprefixer": "6.7.7",
|
||||||
"babel-eslint": "7.2.3",
|
"babel-eslint": "7.2.3",
|
||||||
"babel-loader": "8.2.3",
|
"babel-loader": "8.2.3",
|
||||||
|
|
@ -82,10 +84,10 @@
|
||||||
"iso-639-1": "2.1.13",
|
"iso-639-1": "2.1.13",
|
||||||
"isparta-loader": "2.0.0",
|
"isparta-loader": "2.0.0",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"karma": "3.1.4",
|
"karma": "6.3.17",
|
||||||
"karma-coverage": "1.1.2",
|
"karma-coverage": "1.1.2",
|
||||||
"karma-firefox-launcher": "1.3.0",
|
"karma-firefox-launcher": "1.3.0",
|
||||||
"karma-mocha": "1.3.0",
|
"karma-mocha": "2.0.1",
|
||||||
"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",
|
||||||
|
|
@ -112,7 +114,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": "14.2.4",
|
"vue-loader": "^16.0.0",
|
||||||
"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",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
window.removeEventListener('resize', this.updateMobileState)
|
window.removeEventListener('resize', this.updateMobileState)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
||||||
|
|
@ -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, .fade-leave-active {
|
.fade-enter-from, .fade-leave-active {
|
||||||
opacity: 0
|
opacity: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="app"
|
id="app-loaded"
|
||||||
:style="bgStyle"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="app_bg_wrapper"
|
id="app_bg_wrapper"
|
||||||
class="app-bg-wrapper"
|
class="app-bg-wrapper"
|
||||||
|
:style="bgStyle"
|
||||||
/>
|
/>
|
||||||
<MobileNav v-if="isMobileLayout" />
|
<MobileNav v-if="isMobileLayout" />
|
||||||
<DesktopNav v-else />
|
<DesktopNav v-else />
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
<UserReportingModal />
|
<UserReportingModal />
|
||||||
<PostStatusModal />
|
<PostStatusModal />
|
||||||
<SettingsModal />
|
<SettingsModal />
|
||||||
<portal-target name="modal" />
|
<div id="modal" />
|
||||||
<GlobalNoticeList />
|
<GlobalNoticeList />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
import Vue from 'vue'
|
import { createApp, configureCompat } from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import routes from './routes'
|
import vClickOutside from 'click-outside-vue3'
|
||||||
|
|
||||||
|
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
import App from '../App.vue'
|
import App from '../App.vue'
|
||||||
|
import routes from './routes'
|
||||||
|
import VBodyScrollLock from 'src/directives/body_scroll_lock'
|
||||||
|
|
||||||
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'
|
||||||
|
|
@ -9,6 +15,13 @@ import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
||||||
import { applyTheme } from '../services/style_setter/style_setter.js'
|
import { applyTheme } from '../services/style_setter/style_setter.js'
|
||||||
import FaviconService from '../services/favicon_service/favicon_service.js'
|
import FaviconService from '../services/favicon_service/favicon_service.js'
|
||||||
|
|
||||||
|
// disable compat for certain features
|
||||||
|
configureCompat({
|
||||||
|
COMPONENT_V_MODEL: false,
|
||||||
|
INSTANCE_SET: false,
|
||||||
|
RENDER_FUNCTION: false
|
||||||
|
})
|
||||||
|
|
||||||
let staticInitialResults = null
|
let staticInitialResults = null
|
||||||
|
|
||||||
const parsedInitialResults = () => {
|
const parsedInitialResults = () => {
|
||||||
|
|
@ -367,25 +380,32 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
getTOS({ store })
|
getTOS({ store })
|
||||||
getStickers({ store })
|
getStickers({ store })
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = createRouter({
|
||||||
mode: 'history',
|
history: createWebHistory(),
|
||||||
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 || { x: 0, y: 0 }
|
return savedPosition || { left: 0, top: 0 }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
const app = createApp(App)
|
||||||
return new Vue({
|
|
||||||
router,
|
app.use(router)
|
||||||
store,
|
app.use(store)
|
||||||
i18n,
|
app.use(i18n)
|
||||||
el: '#app',
|
|
||||||
render: h => h(App)
|
app.use(vClickOutside)
|
||||||
})
|
app.use(VBodyScrollLock)
|
||||||
|
|
||||||
|
app.component('FAIcon', FontAwesomeIcon)
|
||||||
|
app.component('FALayers', FontAwesomeLayers)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
|
|
||||||
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
export default afterStoreSetup
|
export default afterStoreSetup
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
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'
|
||||||
|
|
@ -5,8 +6,8 @@ import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
const AuthForm = {
|
const AuthForm = {
|
||||||
name: 'AuthForm',
|
name: 'AuthForm',
|
||||||
render (createElement) {
|
render () {
|
||||||
return createElement('component', { is: this.authForm })
|
return h(resolveComponent(this.authForm))
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
authForm () {
|
authForm () {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
class="avatar"
|
class="avatar"
|
||||||
:user="user"
|
:user="user"
|
||||||
@click.prevent.native="toggleUserExpanded"
|
@click.prevent="toggleUserExpanded"
|
||||||
/>
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const Bookmarks = {
|
||||||
components: {
|
components: {
|
||||||
Timeline
|
Timeline
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ const Chat = {
|
||||||
})
|
})
|
||||||
this.setChatLayout()
|
this.setChatLayout()
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
window.removeEventListener('scroll', this.handleScroll)
|
window.removeEventListener('scroll', this.handleScroll)
|
||||||
window.removeEventListener('resize', this.handleLayoutChange)
|
window.removeEventListener('resize', this.handleLayoutChange)
|
||||||
this.unsetChatLayout()
|
this.unsetChatLayout()
|
||||||
|
|
|
||||||
|
|
@ -26,73 +26,71 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template>
|
<div
|
||||||
<div
|
ref="scrollable"
|
||||||
ref="scrollable"
|
class="scrollable-message-list"
|
||||||
class="scrollable-message-list"
|
:style="{ height: scrollableContainerHeight }"
|
||||||
:style="{ height: scrollableContainerHeight }"
|
@scroll="handleScroll"
|
||||||
@scroll="handleScroll"
|
>
|
||||||
>
|
<template v-if="!errorLoadingChat">
|
||||||
<template v-if="!errorLoadingChat">
|
<ChatMessage
|
||||||
<ChatMessage
|
v-for="chatViewItem in chatViewItems"
|
||||||
v-for="chatViewItem in chatViewItems"
|
:key="chatViewItem.id"
|
||||||
:key="chatViewItem.id"
|
:author="recipient"
|
||||||
:author="recipient"
|
:chat-view-item="chatViewItem"
|
||||||
:chat-view-item="chatViewItem"
|
:hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId"
|
||||||
:hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId"
|
@hover="onMessageHover"
|
||||||
@hover="onMessageHover"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="chat-loading-error"
|
|
||||||
>
|
|
||||||
<div class="alert error">
|
|
||||||
{{ $t('chats.error_loading_chat') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
ref="footer"
|
|
||||||
class="panel-body footer"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="jump-to-bottom-button"
|
|
||||||
:class="{ 'visible': jumpToBottomButtonVisible }"
|
|
||||||
@click="scrollDown({ behavior: 'smooth' })"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<FAIcon icon="chevron-down" />
|
|
||||||
<div
|
|
||||||
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"
|
|
||||||
/>
|
/>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="chat-loading-error"
|
||||||
|
>
|
||||||
|
<div class="alert error">
|
||||||
|
{{ $t('chats.error_loading_chat') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
<div
|
||||||
|
ref="footer"
|
||||||
|
class="panel-body footer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="jump-to-bottom-button"
|
||||||
|
:class="{ 'visible': jumpToBottomButtonVisible }"
|
||||||
|
@click="scrollDown({ behavior: 'smooth' })"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<FAIcon icon="chevron-down" />
|
||||||
|
<div
|
||||||
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
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 Vue.component('chat-title', {
|
export default {
|
||||||
name: 'ChatTitle',
|
name: 'ChatTitle',
|
||||||
components: {
|
components: {
|
||||||
UserAvatar
|
UserAvatar,
|
||||||
|
RichContent
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'user', 'withAvatar'
|
'user', 'withAvatar'
|
||||||
|
|
@ -23,4 +24,4 @@ export default Vue.component('chat-title', {
|
||||||
return generateProfileLink(user.id, user.screen_name)
|
return generateProfileLink(user.id, user.screen_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:checked="checked"
|
:checked="checked"
|
||||||
:indeterminate.prop="indeterminate"
|
:indeterminate="indeterminate"
|
||||||
@change="$emit('change', $event.target.checked)"
|
@change="$emit('change', $event.target.checked)"
|
||||||
>
|
>
|
||||||
<i class="checkbox-indicator" />
|
<i class="checkbox-indicator" />
|
||||||
|
|
|
||||||
|
|
@ -14,25 +14,25 @@
|
||||||
:checked="present"
|
:checked="present"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
class="opt"
|
class="opt"
|
||||||
@change="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
|
@change="$emit('update:modelValue', 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="value || fallback"
|
:value="modelValue || fallback"
|
||||||
:disabled="!present || disabled"
|
:disabled="!present || disabled"
|
||||||
@input="$emit('input', $event.target.value)"
|
@input="$emit('update:modelValue', $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="value || fallback"
|
:value="modelValue || fallback"
|
||||||
:disabled="!present || disabled"
|
:disabled="!present || disabled"
|
||||||
@input="$emit('input', $event.target.value)"
|
@input="$emit('update:modelValue', $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"
|
||||||
value: {
|
modelValue: {
|
||||||
required: false,
|
required: false,
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined
|
default: undefined
|
||||||
|
|
@ -93,16 +93,16 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
present () {
|
present () {
|
||||||
return typeof this.value !== 'undefined'
|
return typeof this.modelValue !== 'undefined'
|
||||||
},
|
},
|
||||||
validColor () {
|
validColor () {
|
||||||
return hex2rgb(this.value || this.fallback)
|
return hex2rgb(this.modelValue || this.fallback)
|
||||||
},
|
},
|
||||||
transparentColor () {
|
transparentColor () {
|
||||||
return this.value === 'transparent'
|
return this.modelValue === 'transparent'
|
||||||
},
|
},
|
||||||
computedColor () {
|
computedColor () {
|
||||||
return this.value && this.value.startsWith('--')
|
return this.modelValue && this.modelValue.startsWith('--')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,20 +27,23 @@
|
||||||
v-if="shouldShowAllConversationButton"
|
v-if="shouldShowAllConversationButton"
|
||||||
class="conversation-dive-to-top-level-box"
|
class="conversation-dive-to-top-level-box"
|
||||||
>
|
>
|
||||||
<i18n
|
<i18n-t
|
||||||
path="status.show_all_conversation_with_icon"
|
keypath="status.show_all_conversation_with_icon"
|
||||||
tag="button"
|
tag="button"
|
||||||
class="button-unstyled -link"
|
class="button-unstyled -link"
|
||||||
@click.prevent="diveToTopLevel"
|
@click.prevent="diveToTopLevel"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<template #icon>
|
||||||
place="icon"
|
<FAIcon
|
||||||
icon="angle-double-left"
|
icon="angle-double-left"
|
||||||
/>
|
/>
|
||||||
<span place="text">
|
</template>
|
||||||
{{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }}
|
<template #text>
|
||||||
</span>
|
<span>
|
||||||
</i18n>
|
{{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="shouldShowAncestors"
|
v-if="shouldShowAncestors"
|
||||||
|
|
@ -96,20 +99,23 @@
|
||||||
<div
|
<div
|
||||||
class="thread-ancestor-dive-box-inner"
|
class="thread-ancestor-dive-box-inner"
|
||||||
>
|
>
|
||||||
<i18n
|
<i18n-t
|
||||||
tag="button"
|
tag="button"
|
||||||
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)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<template #icon>
|
||||||
place="icon"
|
<FAIcon
|
||||||
icon="angle-double-right"
|
icon="angle-double-right"
|
||||||
/>
|
/>
|
||||||
<span place="text">
|
</template>
|
||||||
{{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }}
|
<template #text>
|
||||||
</span>
|
<span>
|
||||||
</i18n>
|
{{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
<search-bar
|
<search-bar
|
||||||
v-if="currentUser || !privateMode"
|
v-if="currentUser || !privateMode"
|
||||||
@toggled="onSearchBarToggled"
|
@toggled="onSearchBarToggled"
|
||||||
@click.stop.native
|
@click.stop
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="button-unstyled nav-icon"
|
class="button-unstyled nav-icon"
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ library.add(
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const EmojiInput = {
|
const EmojiInput = {
|
||||||
|
emits: ['update:modelValue'],
|
||||||
props: {
|
props: {
|
||||||
suggest: {
|
suggest: {
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,8 +58,7 @@ const EmojiInput = {
|
||||||
required: true,
|
required: true,
|
||||||
type: Function
|
type: Function
|
||||||
},
|
},
|
||||||
// TODO VUE3: change to modelValue, change 'input' event to 'input'
|
modelValue: {
|
||||||
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.value && this.caret) {
|
if (this.modelValue && this.caret) {
|
||||||
const word = Completion.wordAtPosition(this.value, this.caret - 1) || {}
|
const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
|
||||||
return word
|
return word
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -189,8 +189,11 @@ const EmojiInput = {
|
||||||
img: imageUrl || ''
|
img: imageUrl || ''
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
suggestions (newValue) {
|
suggestions: {
|
||||||
this.$nextTick(this.resize)
|
handler (newValue) {
|
||||||
|
this.$nextTick(this.resize)
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -225,13 +228,13 @@ const EmojiInput = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
replace (replacement) {
|
replace (replacement) {
|
||||||
const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
|
const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement)
|
||||||
this.$emit('input', newValue)
|
this.$emit('update:modelValue', newValue)
|
||||||
this.caret = 0
|
this.caret = 0
|
||||||
},
|
},
|
||||||
insert ({ insertion, keepOpen, surroundingSpace = true }) {
|
insert ({ insertion, keepOpen, surroundingSpace = true }) {
|
||||||
const before = this.value.substring(0, this.caret) || ''
|
const before = this.modelValue.substring(0, this.caret) || ''
|
||||||
const after = this.value.substring(this.caret) || ''
|
const after = this.modelValue.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
|
||||||
|
|
@ -259,7 +262,7 @@ const EmojiInput = {
|
||||||
after
|
after
|
||||||
].join('')
|
].join('')
|
||||||
this.keepOpen = keepOpen
|
this.keepOpen = keepOpen
|
||||||
this.$emit('input', newValue)
|
this.$emit('update:modelValue', 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()
|
||||||
|
|
@ -278,8 +281,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.value, this.wordAtCaret, replacement)
|
const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement)
|
||||||
this.$emit('input', newValue)
|
this.$emit('update:modelValue', newValue)
|
||||||
this.highlighted = 0
|
this.highlighted = 0
|
||||||
const position = this.wordAtCaret.start + replacement.length
|
const position = this.wordAtCaret.start + replacement.length
|
||||||
|
|
||||||
|
|
@ -455,7 +458,7 @@ const EmojiInput = {
|
||||||
this.showPicker = false
|
this.showPicker = false
|
||||||
this.setCaret(e)
|
this.setCaret(e)
|
||||||
this.resize()
|
this.resize()
|
||||||
this.$emit('input', e.target.value)
|
this.$emit('update:modelValue', e.target.value)
|
||||||
},
|
},
|
||||||
onClickInput (e) {
|
onClickInput (e) {
|
||||||
this.showPicker = false
|
this.showPicker = false
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
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 {
|
||||||
|
|
@ -57,7 +58,7 @@ const EmojiPicker = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
StickerPicker: () => import('../sticker_picker/sticker_picker.vue'),
|
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
|
||||||
Checkbox
|
Checkbox
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -79,7 +80,7 @@ const EmojiPicker = {
|
||||||
},
|
},
|
||||||
highlight (key) {
|
highlight (key) {
|
||||||
const ref = this.$refs['group-' + key]
|
const ref = this.$refs['group-' + key]
|
||||||
const top = ref[0].offsetTop
|
const top = ref.offsetTop
|
||||||
this.setShowStickers(false)
|
this.setShowStickers(false)
|
||||||
this.activeGroup = key
|
this.activeGroup = key
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
@ -96,7 +97,7 @@ const EmojiPicker = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
triggerLoadMore (target) {
|
triggerLoadMore (target) {
|
||||||
const ref = this.$refs['group-end-custom'][0]
|
const ref = this.$refs['group-end-custom']
|
||||||
if (!ref) return
|
if (!ref) return
|
||||||
const bottom = ref.offsetTop + ref.offsetHeight
|
const bottom = ref.offsetTop + ref.offsetHeight
|
||||||
|
|
||||||
|
|
@ -119,7 +120,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[0].offsetTop <= top) {
|
if (ref.offsetTop <= top) {
|
||||||
this.activeGroup = group.id
|
this.activeGroup = group.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,8 @@ const Exporter = {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'export.csv'
|
default: 'export.csv'
|
||||||
},
|
},
|
||||||
exportButtonLabel: {
|
exportButtonLabel: { type: String },
|
||||||
type: String,
|
processingMessage: { type: String }
|
||||||
default () {
|
|
||||||
return this.$t('exporter.export')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
processingMessage: {
|
|
||||||
type: String,
|
|
||||||
default () {
|
|
||||||
return this.$t('exporter.processing')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,14 @@
|
||||||
spin
|
spin
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span>{{ processingMessage }}</span>
|
<span>{{ processingMessage || $t('exporter.processing') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
@click="process"
|
@click="process"
|
||||||
>
|
>
|
||||||
{{ exportButtonLabel }}
|
{{ exportButtonLabel || $t('exporter.export') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { set } from 'vue'
|
import { set } from 'lodash'
|
||||||
import Select from '../select/select.vue'
|
import Select from '../select/select.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,14 @@
|
||||||
class="opt exlcude-disabled"
|
class="opt exlcude-disabled"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="present"
|
:checked="present"
|
||||||
@input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
|
@input="$emit('update:modelValue', 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"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Attachment from '../attachment/attachment.vue'
|
import Attachment from '../attachment/attachment.vue'
|
||||||
import { sumBy } from 'lodash'
|
import { sumBy, set } from 'lodash'
|
||||||
|
|
||||||
const Gallery = {
|
const Gallery = {
|
||||||
props: [
|
props: [
|
||||||
|
|
@ -85,7 +85,7 @@ const Gallery = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onNaturalSizeLoad ({ id, width, height }) {
|
onNaturalSizeLoad ({ id, width, height }) {
|
||||||
this.$set(this.sizes, id, { width, height })
|
set(this.sizes, id, { width, height })
|
||||||
},
|
},
|
||||||
rowStyle (row) {
|
rowStyle (row) {
|
||||||
if (row.audio) {
|
if (row.audio) {
|
||||||
|
|
|
||||||
|
|
@ -15,24 +15,9 @@ const Importer = {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
submitButtonLabel: {
|
submitButtonLabel: { type: String },
|
||||||
type: String,
|
successMessage: { type: String },
|
||||||
default () {
|
errorMessage: { type: String }
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,21 @@
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
>
|
>
|
||||||
{{ submitButtonLabel }}
|
{{ submitButtonLabel || $t('importer.submit') }}
|
||||||
</button>
|
</button>
|
||||||
<div v-if="success">
|
<div v-if="success">
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="times"
|
icon="times"
|
||||||
@click="dismiss"
|
@click="dismiss"
|
||||||
/>
|
/>
|
||||||
<p>{{ successMessage }}</p>
|
<p>{{ successMessage || $t('importer.success') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="error">
|
<div v-else-if="error">
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="times"
|
icon="times"
|
||||||
@click="dismiss"
|
@click="dismiss"
|
||||||
/>
|
/>
|
||||||
<p>{{ errorMessage }}</p>
|
<p>{{ errorMessage || $t('importer.error') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
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'],
|
||||||
|
|
@ -20,7 +21,8 @@ const Interactions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Notifications
|
Notifications,
|
||||||
|
TabSwitcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const MobilePostStatusButton = {
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', this.handleOSK)
|
window.addEventListener('resize', this.handleOSK)
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
if (this.autohideFloatingPostButton) {
|
if (this.autohideFloatingPostButton) {
|
||||||
this.deactivateFloatingPostButtonAutohide()
|
this.deactivateFloatingPostButtonAutohide()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isLoggedIn">
|
<button
|
||||||
<button
|
v-if="isLoggedIn"
|
||||||
class="button-default new-status-button"
|
class="MobilePostButton 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>
|
||||||
|
|
@ -15,25 +14,27 @@
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.new-status-button {
|
.MobilePostButton {
|
||||||
width: 5em;
|
&.button-default {
|
||||||
height: 5em;
|
width: 5em;
|
||||||
border-radius: 100%;
|
height: 5em;
|
||||||
position: fixed;
|
border-radius: 100%;
|
||||||
bottom: 1.5em;
|
position: fixed;
|
||||||
right: 1.5em;
|
bottom: 1.5em;
|
||||||
// TODO: this needs its own color, it has to stand out enough and link color
|
right: 1.5em;
|
||||||
// is not very optimal for this particular use.
|
// TODO: this needs its own color, it has to stand out enough and link color
|
||||||
background-color: $fallback--fg;
|
// is not very optimal for this particular use.
|
||||||
background-color: var(--btn, $fallback--fg);
|
background-color: $fallback--fg;
|
||||||
display: flex;
|
background-color: var(--btn, $fallback--fg);
|
||||||
justify-content: center;
|
display: flex;
|
||||||
align-items: center;
|
justify-content: center;
|
||||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
|
align-items: center;
|
||||||
z-index: 10;
|
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||||
|
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%);
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</Popover>
|
</Popover>
|
||||||
<portal to="modal">
|
<teleport 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>
|
||||||
</portal>
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="avatar-container"
|
class="avatar-container"
|
||||||
:href="notification.from_profile.statusnet_profile_url"
|
:href="$router.resolve(userProfileLink).href"
|
||||||
@click.stop.prevent.capture="toggleUserExpanded"
|
@click.stop.prevent.capture="toggleUserExpanded"
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
|
|
@ -65,12 +65,16 @@
|
||||||
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'">
|
||||||
|
|
@ -79,6 +83,7 @@
|
||||||
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'">
|
||||||
|
|
@ -86,6 +91,7 @@
|
||||||
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'">
|
||||||
|
|
@ -93,6 +99,7 @@
|
||||||
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'">
|
||||||
|
|
@ -100,13 +107,14 @@
|
||||||
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 path="notifications.reacted_with">
|
<i18n-t keypath="notifications.reacted_with">
|
||||||
<span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
|
<span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
|
||||||
</i18n>
|
</i18n-t>
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
:checked="present"
|
:checked="present"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
class="opt"
|
class="opt"
|
||||||
@change="$emit('input', !present ? fallback : undefined)"
|
@change="$emit('update:modelValue', !present ? fallback : undefined)"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
:id="name"
|
:id="name"
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
max="1"
|
max="1"
|
||||||
min="0"
|
min="0"
|
||||||
step=".05"
|
step=".05"
|
||||||
@input="$emit('input', $event.target.value)"
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export default {
|
||||||
}
|
}
|
||||||
this.$store.dispatch('trackPoll', this.pollId)
|
this.$store.dispatch('trackPoll', this.pollId)
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
this.$store.dispatch('untrackPoll', this.pollId)
|
this.$store.dispatch('untrackPoll', this.pollId)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
||||||
|
|
@ -71,13 +71,13 @@
|
||||||
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }} ·
|
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }} ·
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
|
<i18n-t :keypath="expired ? 'polls.expired' : 'polls.expires_in'">
|
||||||
<Timeago
|
<Timeago
|
||||||
:time="expiresAt"
|
:time="expiresAt"
|
||||||
:auto-update="60"
|
:auto-update="60"
|
||||||
:now-threshold="0"
|
:now-threshold="0"
|
||||||
/>
|
/>
|
||||||
</i18n>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
:max="maxExpirationInCurrentUnit"
|
:max="maxExpirationInCurrentUnit"
|
||||||
@change="expiryAmountChange"
|
@change="expiryAmountChange"
|
||||||
>
|
>
|
||||||
|
{{ ' ' }}
|
||||||
<Select
|
<Select
|
||||||
v-model="expiryUnit"
|
v-model="expiryUnit"
|
||||||
unstyled="true"
|
unstyled="true"
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ const Popover = {
|
||||||
created () {
|
created () {
|
||||||
document.addEventListener('click', this.onClickOutside)
|
document.addEventListener('click', this.onClickOutside)
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
document.removeEventListener('click', this.onClickOutside)
|
document.removeEventListener('click', this.onClickOutside)
|
||||||
this.hidePopover()
|
this.hidePopover()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
|
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<i18n
|
<i18n-t
|
||||||
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
|
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
|
||||||
path="post_status.account_not_locked_warning"
|
keypath="post_status.account_not_locked_warning"
|
||||||
tag="p"
|
tag="p"
|
||||||
class="visibility-notice"
|
class="visibility-notice"
|
||||||
>
|
>
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
>
|
>
|
||||||
{{ $t('post_status.account_not_locked_warning_link') }}
|
{{ $t('post_status.account_not_locked_warning_link') }}
|
||||||
</button>
|
</button>
|
||||||
</i18n>
|
</i18n-t>
|
||||||
<p
|
<p
|
||||||
v-if="!hideScopeNotice && newStatus.visibility === 'public'"
|
v-if="!hideScopeNotice && newStatus.visibility === 'public'"
|
||||||
class="visibility-notice notice-dismissible"
|
class="visibility-notice notice-dismissible"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const PublicAndExternalTimeline = {
|
||||||
created () {
|
created () {
|
||||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
|
this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
|
this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const PublicTimeline = {
|
||||||
created () {
|
created () {
|
||||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
|
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
this.$store.dispatch('stopFetchingTimeline', 'public')
|
this.$store.dispatch('stopFetchingTimeline', 'public')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
class="opt"
|
class="opt"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="present"
|
:checked="present"
|
||||||
@input="$emit('input', !present ? fallback : undefined)"
|
@input="$emit('update:modelValue', !present ? fallback : undefined)"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
v-if="typeof fallback !== 'undefined'"
|
v-if="typeof fallback !== 'undefined'"
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
:max="max || hardMax || 100"
|
:max="max || hardMax || 100"
|
||||||
:min="min || hardMin || 0"
|
:min="min || hardMin || 0"
|
||||||
:step="step || 1"
|
:step="step || 1"
|
||||||
@input="$emit('input', $event.target.value)"
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
:id="name"
|
:id="name"
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
:max="hardMax"
|
:max="hardMax"
|
||||||
:min="hardMin"
|
:min="hardMin"
|
||||||
:step="step || 1"
|
:step="step || 1"
|
||||||
@input="$emit('input', $event.target.value)"
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { validationMixin } from 'vuelidate'
|
import useVuelidate from '@vuelidate/core'
|
||||||
import { required, requiredIf, sameAs } from 'vuelidate/lib/validators'
|
import { required, requiredIf, sameAs } from '@vuelidate/validators'
|
||||||
import { mapActions, mapState } from 'vuex'
|
import { mapActions, mapState } from 'vuex'
|
||||||
|
|
||||||
const registration = {
|
const registration = {
|
||||||
mixins: [validationMixin],
|
setup () { return { v$: useVuelidate() } },
|
||||||
data: () => ({
|
data: () => ({
|
||||||
user: {
|
user: {
|
||||||
email: '',
|
email: '',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
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'
|
||||||
|
|
@ -27,7 +26,7 @@ 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 Vue.component('RichContent', {
|
export default {
|
||||||
name: 'RichContent',
|
name: 'RichContent',
|
||||||
props: {
|
props: {
|
||||||
// Original html content
|
// Original html content
|
||||||
|
|
@ -58,7 +57,7 @@ export default Vue.component('RichContent', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// NEVER EVER TOUCH DATA INSIDE RENDER
|
// NEVER EVER TOUCH DATA INSIDE RENDER
|
||||||
render (h) {
|
render () {
|
||||||
// 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
|
||||||
|
|
@ -76,18 +75,18 @@ export default Vue.component('RichContent', {
|
||||||
|
|
||||||
const renderImage = (tag) => {
|
const renderImage = (tag) => {
|
||||||
return <StillImage
|
return <StillImage
|
||||||
{...{ attrs: getAttrs(tag) }}
|
{...getAttrs(tag)}
|
||||||
class="img"
|
class="img"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderHashtag = (attrs, children, encounteredTextReverse) => {
|
const renderHashtag = (attrs, children, encounteredTextReverse) => {
|
||||||
const linkData = getLinkData(attrs, children, tagsIndex++)
|
const { index, ...linkData } = getLinkData(attrs, children, tagsIndex++)
|
||||||
writtenTags.push(linkData)
|
writtenTags.push(linkData)
|
||||||
if (!encounteredTextReverse) {
|
if (!encounteredTextReverse) {
|
||||||
lastTags.push(linkData)
|
lastTags.push(linkData)
|
||||||
}
|
}
|
||||||
return <HashtagLink {...{ props: linkData }}/>
|
return <HashtagLink { ...linkData }/>
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderMention = (attrs, children) => {
|
const renderMention = (attrs, children) => {
|
||||||
|
|
@ -222,7 +221,7 @@ export default Vue.component('RichContent', {
|
||||||
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>
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +234,7 @@ export default Vue.component('RichContent', {
|
||||||
const newChildren = Array.isArray(children)
|
const newChildren = Array.isArray(children)
|
||||||
? [...children].reverse().map(processItemReverse).reverse()
|
? [...children].reverse().map(processItemReverse).reverse()
|
||||||
: children
|
: children
|
||||||
return <Tag {...{ attrs: getAttrs(opener) }}>
|
return <Tag {...getAttrs(opener)}>
|
||||||
{ newChildren }
|
{ newChildren }
|
||||||
</Tag>
|
</Tag>
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -266,7 +265,7 @@ export default Vue.component('RichContent', {
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
const getLinkData = (attrs, children, index) => {
|
const getLinkData = (attrs, children, index) => {
|
||||||
const stripTags = (item) => {
|
const stripTags = (item) => {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
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"
|
||||||
|
|
@ -29,6 +30,7 @@
|
||||||
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"
|
||||||
|
|
@ -42,6 +44,7 @@
|
||||||
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"
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,9 @@ library.add(
|
||||||
)
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
model: {
|
emits: ['update:modelValue'],
|
||||||
prop: 'value',
|
|
||||||
event: 'change'
|
|
||||||
},
|
|
||||||
props: [
|
props: [
|
||||||
'value',
|
'modelValue',
|
||||||
'disabled',
|
'disabled',
|
||||||
'unstyled',
|
'unstyled',
|
||||||
'kind'
|
'kind'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
class="Select input"
|
class="Select input"
|
||||||
|
|
@ -6,11 +5,12 @@
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:value="value"
|
:value="modelValue"
|
||||||
@change="$emit('change', $event.target.value)"
|
@change="$emit('update:modelValue', $event.target.value)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</select>
|
</select>
|
||||||
|
{{ ' ' }}
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="select-down-icon"
|
class="select-down-icon"
|
||||||
icon="chevron-down"
|
icon="chevron-down"
|
||||||
|
|
@ -23,7 +23,8 @@
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.Select {
|
/* TODO fix order of styles */
|
||||||
|
label.Select {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
|
{{ ' ' }}
|
||||||
<ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
|
<ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@
|
||||||
class="ChoiceSetting"
|
class="ChoiceSetting"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
{{ ' ' }}
|
||||||
<Select
|
<Select
|
||||||
:value="state"
|
:modelValue="state"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@change="update"
|
@update:modelValue="update"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="option in options"
|
v-for="option in options"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
:value="state"
|
:value="state"
|
||||||
@change="update"
|
@change="update"
|
||||||
>
|
>
|
||||||
|
{{ ' ' }}
|
||||||
<ModifiedIndicator :changed="isChanged" />
|
<ModifiedIndicator :changed="isChanged" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
{{ $t('settings.settings') }}
|
{{ $t('settings.settings') }}
|
||||||
</span>
|
</span>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<template v-if="currentSaveStateNotice">
|
<div 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>
|
||||||
</template>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
: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"
|
||||||
/>
|
/>
|
||||||
|
|
@ -109,7 +110,7 @@
|
||||||
</template>
|
</template>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<Checkbox v-model="expertLevel">
|
<Checkbox :checked="!!expertLevel" @change="expertLevel = Number($event)">
|
||||||
{{ $t("settings.expert_mode") }}
|
{{ $t("settings.expert_mode") }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
|
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||||
|
|
||||||
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,6 +53,9 @@ 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: {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
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')"
|
||||||
|
|
|
||||||
|
|
@ -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.js'
|
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||||
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'
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,14 @@
|
||||||
{{ $t('settings.style.preview.content') }}
|
{{ $t('settings.style.preview.content') }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<i18n path="settings.style.preview.text">
|
<i18n-t keypath="settings.style.preview.text">
|
||||||
<code style="font-family: var(--postCodeFont)">
|
<code style="font-family: var(--postCodeFont)">
|
||||||
{{ $t('settings.style.preview.mono') }}
|
{{ $t('settings.style.preview.mono') }}
|
||||||
</code>
|
</code>
|
||||||
<a style="color: var(--link)">
|
<a style="color: var(--link)">
|
||||||
{{ $t('settings.style.preview.link') }}
|
{{ $t('settings.style.preview.link') }}
|
||||||
</a>
|
</a>
|
||||||
</i18n>
|
</i18n-t>
|
||||||
|
|
||||||
<div class="icons">
|
<div class="icons">
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
|
@ -72,15 +72,15 @@
|
||||||
:^)
|
:^)
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<i18n
|
<i18n-t
|
||||||
path="settings.style.preview.fine_print"
|
keypath="settings.style.preview.fine_print"
|
||||||
tag="span"
|
tag="span"
|
||||||
class="faint"
|
class="faint"
|
||||||
>
|
>
|
||||||
<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>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator" />
|
<div class="separator" />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { set, delete as del } from 'vue'
|
|
||||||
import {
|
import {
|
||||||
rgb2hex,
|
rgb2hex,
|
||||||
hex2rgb,
|
hex2rgb,
|
||||||
|
|
@ -34,7 +33,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.js'
|
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||||
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'
|
||||||
|
|
||||||
|
|
@ -320,9 +319,9 @@ export default {
|
||||||
},
|
},
|
||||||
set (val) {
|
set (val) {
|
||||||
if (val) {
|
if (val) {
|
||||||
set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _)))
|
this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _))
|
||||||
} else {
|
} else {
|
||||||
del(this.shadowsLocal, this.shadowSelected)
|
delete this.shadowsLocal[this.shadowSelected]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -334,7 +333,7 @@ export default {
|
||||||
return this.shadowsLocal[this.shadowSelected]
|
return this.shadowsLocal[this.shadowSelected]
|
||||||
},
|
},
|
||||||
set (v) {
|
set (v) {
|
||||||
set(this.shadowsLocal, this.shadowSelected, v)
|
this.shadowsLocal[this.shadowSelected] = v
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
themeValid () {
|
themeValid () {
|
||||||
|
|
@ -557,7 +556,7 @@ export default {
|
||||||
.filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
|
.filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
|
||||||
.filter(_ => !v1OnlyNames.includes(_))
|
.filter(_ => !v1OnlyNames.includes(_))
|
||||||
.forEach(key => {
|
.forEach(key => {
|
||||||
set(this.$data, key, undefined)
|
this.$data[key] = undefined
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -565,7 +564,7 @@ export default {
|
||||||
Object.keys(this.$data)
|
Object.keys(this.$data)
|
||||||
.filter(_ => _.endsWith('RadiusLocal'))
|
.filter(_ => _.endsWith('RadiusLocal'))
|
||||||
.forEach(key => {
|
.forEach(key => {
|
||||||
set(this.$data, key, undefined)
|
this.$data[key] = undefined
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -573,7 +572,7 @@ export default {
|
||||||
Object.keys(this.$data)
|
Object.keys(this.$data)
|
||||||
.filter(_ => _.endsWith('OpacityLocal'))
|
.filter(_ => _.endsWith('OpacityLocal'))
|
||||||
.forEach(key => {
|
.forEach(key => {
|
||||||
set(this.$data, key, undefined)
|
this.$data[key] = undefined
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -903,6 +903,7 @@
|
||||||
<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"
|
||||||
|
|
@ -924,6 +925,7 @@
|
||||||
>
|
>
|
||||||
{{ $t('settings.style.shadows.override') }}
|
{{ $t('settings.style.shadows.override') }}
|
||||||
</label>
|
</label>
|
||||||
|
{{ ' ' }}
|
||||||
<input
|
<input
|
||||||
id="override"
|
id="override"
|
||||||
v-model="currentShadowOverriden"
|
v-model="currentShadowOverriden"
|
||||||
|
|
@ -949,27 +951,27 @@
|
||||||
:fallback="currentShadowFallback"
|
:fallback="currentShadowFallback"
|
||||||
/>
|
/>
|
||||||
<div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
|
<div v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'">
|
||||||
<i18n
|
<i18n-t
|
||||||
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>
|
</i18n-t>
|
||||||
<p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
|
<p>{{ $t('settings.style.shadows.filter_hint.avatar_inset') }}</p>
|
||||||
<i18n
|
<i18n-t
|
||||||
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>
|
</i18n-t>
|
||||||
<i18n
|
<i18n-t
|
||||||
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>
|
</i18n-t>
|
||||||
<p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
|
<p>{{ $t('settings.style.shadows.filter_hint.spread_zero') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script src="./version_tab.js">
|
<script src="./version_tab.js" />
|
||||||
|
|
|
||||||
|
|
@ -204,12 +204,12 @@
|
||||||
v-model="selected.alpha"
|
v-model="selected.alpha"
|
||||||
:disabled="!present"
|
:disabled="!present"
|
||||||
/>
|
/>
|
||||||
<i18n
|
<i18n-t
|
||||||
path="settings.style.shadows.hintV3"
|
keypath="settings.style.shadows.hintV3"
|
||||||
tag="p"
|
tag="p"
|
||||||
>
|
>
|
||||||
<code>--variable,mod</code>
|
<code>--variable,mod</code>
|
||||||
</i18n>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -389,6 +389,9 @@ 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: {
|
||||||
|
|
@ -478,11 +481,6 @@ 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<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 }]"
|
||||||
>
|
>
|
||||||
|
|
@ -120,9 +121,9 @@
|
||||||
v-if="!noHeading"
|
v-if="!noHeading"
|
||||||
class="left-side"
|
class="left-side"
|
||||||
>
|
>
|
||||||
<router-link
|
<a
|
||||||
:to="userProfileLink"
|
:href="$router.resolve(userProfileLink).href"
|
||||||
@click.stop.prevent.capture.native="toggleUserExpanded"
|
@click.stop.prevent.capture="toggleUserExpanded"
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
class="post-avatar"
|
class="post-avatar"
|
||||||
|
|
@ -131,7 +132,7 @@
|
||||||
:better-shadow="betterShadow"
|
:better-shadow="betterShadow"
|
||||||
:user="status.user"
|
:user="status.user"
|
||||||
/>
|
/>
|
||||||
</router-link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-side">
|
<div class="right-side">
|
||||||
<UserCard
|
<UserCard
|
||||||
|
|
@ -191,7 +192,7 @@
|
||||||
<span
|
<span
|
||||||
v-if="status.visibility"
|
v-if="status.visibility"
|
||||||
class="visibility-icon"
|
class="visibility-icon"
|
||||||
:title="status.visibility | capitalize"
|
:title="visibilityLocalized"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
|
|
@ -274,6 +275,7 @@
|
||||||
icon="reply"
|
icon="reply"
|
||||||
flip="horizontal"
|
flip="horizontal"
|
||||||
/>
|
/>
|
||||||
|
{{ ' ' }}
|
||||||
<span
|
<span
|
||||||
class="reply-to-text"
|
class="reply-to-text"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { find } from 'lodash'
|
import { find } from 'lodash'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faCircleNotch
|
faCircleNotch
|
||||||
|
|
@ -22,8 +23,8 @@ const StatusPopover = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Status: () => import('../status/status.vue'),
|
Status: defineAsyncComponent(() => import('../status/status.vue')),
|
||||||
Popover: () => import('../popover/popover.vue')
|
Popover: defineAsyncComponent(() => import('../popover/popover.vue'))
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
enter () {
|
enter () {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-env browser */
|
/* eslint-env browser */
|
||||||
import statusPosterService from '../../services/status_poster/status_poster.service.js'
|
import statusPosterService from '../../services/status_poster/status_poster.service.js'
|
||||||
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
|
||||||
|
|
||||||
const StickerPicker = {
|
const StickerPicker = {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import Vue from 'vue'
|
// eslint-disable-next-line no-unused
|
||||||
|
import { h, Fragment } from 'vue'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
import './tab_switcher.scss'
|
import './tab_switcher.scss'
|
||||||
|
|
||||||
export default Vue.component('tab-switcher', {
|
const findFirstUsable = (slots) => slots.findIndex(_ => _.props)
|
||||||
|
|
||||||
|
export default {
|
||||||
name: 'TabSwitcher',
|
name: 'TabSwitcher',
|
||||||
props: {
|
props: {
|
||||||
renderOnlyFocused: {
|
renderOnlyFocused: {
|
||||||
|
|
@ -31,33 +34,35 @@ export default Vue.component('tab-switcher', {
|
||||||
required: false,
|
required: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
bodyScrollLock: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
active: this.$slots.default.findIndex(_ => _.tag)
|
active: findFirstUsable(this.slots())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
activeIndex () {
|
activeIndex () {
|
||||||
// In case of controlled component
|
// In case of controlled component
|
||||||
if (this.activeTab) {
|
if (this.activeTab) {
|
||||||
return this.$slots.default.findIndex(slot => this.activeTab === slot.key)
|
return this.slots().findIndex(slot => this.activeTab === slot.key)
|
||||||
} else {
|
} else {
|
||||||
return this.active
|
return this.active
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
settingsModalVisible () {
|
|
||||||
return this.settingsModalState === 'visible'
|
|
||||||
},
|
|
||||||
...mapState({
|
...mapState({
|
||||||
settingsModalState: state => state.interface.settingsModalState
|
settingsModalState: state => state.interface.settingsModalState
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
const currentSlot = this.$slots.default[this.active]
|
const currentSlot = this.slots()[this.active]
|
||||||
if (!currentSlot.tag) {
|
if (!currentSlot.props) {
|
||||||
this.active = this.$slots.default.findIndex(_ => _.tag)
|
this.active = findFirstUsable(this.slots())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -67,9 +72,16 @@ export default Vue.component('tab-switcher', {
|
||||||
this.setTab(index)
|
this.setTab(index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// DO NOT put it to computed, it doesn't work (caching?)
|
||||||
|
slots () {
|
||||||
|
if (this.$slots.default()[0].type === Fragment) {
|
||||||
|
return this.$slots.default()[0].children
|
||||||
|
}
|
||||||
|
return this.$slots.default()
|
||||||
|
},
|
||||||
setTab (index) {
|
setTab (index) {
|
||||||
if (typeof this.onSwitch === 'function') {
|
if (typeof this.onSwitch === 'function') {
|
||||||
this.onSwitch.call(null, this.$slots.default[index].key)
|
this.onSwitch.call(null, this.slots()[index].key)
|
||||||
}
|
}
|
||||||
this.active = index
|
this.active = index
|
||||||
if (this.scrollableTabs) {
|
if (this.scrollableTabs) {
|
||||||
|
|
@ -77,27 +89,28 @@ export default Vue.component('tab-switcher', {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render (h) {
|
render () {
|
||||||
const tabs = this.$slots.default
|
const tabs = this.slots()
|
||||||
.map((slot, index) => {
|
.map((slot, index) => {
|
||||||
if (!slot.tag) return
|
const props = slot.props
|
||||||
|
if (!props) return
|
||||||
const classesTab = ['tab', 'button-default']
|
const classesTab = ['tab', 'button-default']
|
||||||
const classesWrapper = ['tab-wrapper']
|
const classesWrapper = ['tab-wrapper']
|
||||||
if (this.activeIndex === index) {
|
if (this.activeIndex === index) {
|
||||||
classesTab.push('active')
|
classesTab.push('active')
|
||||||
classesWrapper.push('active')
|
classesWrapper.push('active')
|
||||||
}
|
}
|
||||||
if (slot.data.attrs.image) {
|
if (props.image) {
|
||||||
return (
|
return (
|
||||||
<div class={classesWrapper.join(' ')}>
|
<div class={classesWrapper.join(' ')}>
|
||||||
<button
|
<button
|
||||||
disabled={slot.data.attrs.disabled}
|
disabled={props.disabled}
|
||||||
onClick={this.clickTab(index)}
|
onClick={this.clickTab(index)}
|
||||||
class={classesTab.join(' ')}
|
class={classesTab.join(' ')}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/>
|
<img src={props.image} title={props['image-tooltip']}/>
|
||||||
{slot.data.attrs.label ? '' : slot.data.attrs.label}
|
{props.label ? '' : props.label}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -105,25 +118,26 @@ export default Vue.component('tab-switcher', {
|
||||||
return (
|
return (
|
||||||
<div class={classesWrapper.join(' ')}>
|
<div class={classesWrapper.join(' ')}>
|
||||||
<button
|
<button
|
||||||
disabled={slot.data.attrs.disabled}
|
disabled={props.disabled}
|
||||||
onClick={this.clickTab(index)}
|
onClick={this.clickTab(index)}
|
||||||
class={classesTab.join(' ')}
|
class={classesTab.join(' ')}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{!slot.data.attrs.icon ? '' : (<FAIcon class="tab-icon" size="2x" fixed-width icon={slot.data.attrs.icon}/>)}
|
{!props.icon ? '' : (<FAIcon class="tab-icon" size="2x" fixed-width icon={props.icon}/>)}
|
||||||
<span class="text">
|
<span class="text">
|
||||||
{slot.data.attrs.label}
|
{props.label}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const contents = this.$slots.default.map((slot, index) => {
|
const contents = this.slots().map((slot, index) => {
|
||||||
if (!slot.tag) return
|
const props = slot.props
|
||||||
|
if (!props) return
|
||||||
const active = this.activeIndex === index
|
const active = this.activeIndex === index
|
||||||
const classes = [ active ? 'active' : 'hidden' ]
|
const classes = [ active ? 'active' : 'hidden' ]
|
||||||
if (slot.data.attrs.fullHeight) {
|
if (props.fullHeight) {
|
||||||
classes.push('full-height')
|
classes.push('full-height')
|
||||||
}
|
}
|
||||||
const renderSlot = (!this.renderOnlyFocused || active)
|
const renderSlot = (!this.renderOnlyFocused || active)
|
||||||
|
|
@ -134,7 +148,7 @@ export default Vue.component('tab-switcher', {
|
||||||
<div class={classes}>
|
<div class={classes}>
|
||||||
{
|
{
|
||||||
this.sideTabBar
|
this.sideTabBar
|
||||||
? <h1 class="mobile-label">{slot.data.attrs.label}</h1>
|
? <h1 class="mobile-label">{props.label}</h1>
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
{renderSlot}
|
{renderSlot}
|
||||||
|
|
@ -147,10 +161,14 @@ export default Vue.component('tab-switcher', {
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
{tabs}
|
{tabs}
|
||||||
</div>
|
</div>
|
||||||
<div ref="contents" class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')} v-body-scroll-lock={this.settingsModalVisible}>
|
<div
|
||||||
|
ref="contents"
|
||||||
|
class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}
|
||||||
|
v-body-scroll-lock={this.bodyScrollLock}
|
||||||
|
>
|
||||||
{contents}
|
{contents}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
@ -166,13 +166,6 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 6px 1em;
|
padding: 6px 1em;
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--tab, $fallback--fg);
|
|
||||||
|
|
||||||
&, &:active .tab-icon {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--tabText, $fallback--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.active) {
|
&:not(.active) {
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const TagTimeline = {
|
||||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'tag', tag: this.tag })
|
this.$store.dispatch('startFetchingTimeline', { timeline: 'tag', tag: this.tag })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
this.$store.dispatch('stopFetchingTimeline', 'tag')
|
this.$store.dispatch('stopFetchingTimeline', 'tag')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,36 +74,42 @@
|
||||||
v-if="currentReplies.length && !threadShowing"
|
v-if="currentReplies.length && !threadShowing"
|
||||||
class="thread-tree-replies thread-tree-replies-hidden"
|
class="thread-tree-replies thread-tree-replies-hidden"
|
||||||
>
|
>
|
||||||
<i18n
|
<i18n-t
|
||||||
v-if="simple"
|
v-if="simple"
|
||||||
tag="button"
|
tag="button"
|
||||||
path="status.thread_follow_with_icon"
|
keypath="status.thread_follow_with_icon"
|
||||||
class="button-unstyled -link thread-tree-show-replies-button"
|
class="button-unstyled -link thread-tree-show-replies-button"
|
||||||
@click.prevent="dive(status.id)"
|
@click.prevent="dive(status.id)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<template #icon>
|
||||||
place="icon"
|
<FAIcon
|
||||||
icon="angle-double-right"
|
icon="angle-double-right"
|
||||||
/>
|
/>
|
||||||
<span place="text">
|
</template>
|
||||||
{{ $tc('status.thread_follow', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id] }) }}
|
<template #text>
|
||||||
</span>
|
<span>
|
||||||
</i18n>
|
{{ $tc('status.thread_follow', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id] }) }}
|
||||||
<i18n
|
</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<i18n-t
|
||||||
v-else
|
v-else
|
||||||
tag="button"
|
tag="button"
|
||||||
path="status.thread_show_full_with_icon"
|
keypath="status.thread_show_full_with_icon"
|
||||||
class="button-unstyled -link thread-tree-show-replies-button"
|
class="button-unstyled -link thread-tree-show-replies-button"
|
||||||
@click.prevent="showThreadRecursively(status.id)"
|
@click.prevent="showThreadRecursively(status.id)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<template #icon>
|
||||||
place="icon"
|
<FAIcon
|
||||||
icon="angle-double-down"
|
icon="angle-double-down"
|
||||||
/>
|
/>
|
||||||
<span place="text">
|
</template>
|
||||||
{{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }}
|
<template #text>
|
||||||
</span>
|
<span>
|
||||||
</i18n>
|
{{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default {
|
||||||
created () {
|
created () {
|
||||||
this.refreshRelativeTimeObject()
|
this.refreshRelativeTimeObject()
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
clearTimeout(this.interval)
|
clearTimeout(this.interval)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ const Timeline = {
|
||||||
window.addEventListener('keydown', this.handleShortKey)
|
window.addEventListener('keydown', this.handleShortKey)
|
||||||
setTimeout(this.determineVisibleStatuses, 250)
|
setTimeout(this.determineVisibleStatuses, 250)
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
window.removeEventListener('scroll', this.handleScroll)
|
window.removeEventListener('scroll', this.handleScroll)
|
||||||
window.removeEventListener('keydown', this.handleShortKey)
|
window.removeEventListener('keydown', this.handleShortKey)
|
||||||
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
|
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@
|
||||||
class="userHighlightCl"
|
class="userHighlightCl"
|
||||||
type="color"
|
type="color"
|
||||||
>
|
>
|
||||||
|
{{ ' ' }}
|
||||||
<Select
|
<Select
|
||||||
:id="'userHighlightSel'+user.id"
|
:id="'userHighlightSel'+user.id"
|
||||||
v-model="userHighlightType"
|
v-model="userHighlightType"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
|
@ -11,8 +13,8 @@ const UserListPopover = {
|
||||||
'users'
|
'users'
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
Popover: () => import('../popover/popover.vue'),
|
Popover: defineAsyncComponent(() => import('../popover/popover.vue')),
|
||||||
UserAvatar: () => import('../user_avatar/user_avatar.vue')
|
UserAvatar: defineAsyncComponent(() => import('../user_avatar/user_avatar.vue'))
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
usersCapped () {
|
usersCapped () {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="user-panel">
|
<div class="user-panel">
|
||||||
<div
|
<div
|
||||||
v-if="signedIn"
|
v-if="signedIn"
|
||||||
key="user-panel"
|
key="user-panel-signed"
|
||||||
class="panel panel-default signed-in"
|
class="panel panel-default signed-in"
|
||||||
>
|
>
|
||||||
<UserCard
|
<UserCard
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import UserCard from '../user_card/user_card.vue'
|
||||||
import FollowCard from '../follow_card/follow_card.vue'
|
import FollowCard from '../follow_card/follow_card.vue'
|
||||||
import Timeline from '../timeline/timeline.vue'
|
import Timeline from '../timeline/timeline.vue'
|
||||||
import Conversation from '../conversation/conversation.vue'
|
import Conversation from '../conversation/conversation.vue'
|
||||||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
|
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||||
import List from '../list/list.vue'
|
import List from '../list/list.vue'
|
||||||
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
import withLoadMore from '../../hocs/with_load_more/with_load_more'
|
||||||
|
|
@ -47,7 +47,7 @@ const UserProfile = {
|
||||||
this.load(routeParams.name || routeParams.id)
|
this.load(routeParams.name || routeParams.id)
|
||||||
this.tab = get(this.$route, 'query.tab', defaultTabKey)
|
this.tab = get(this.$route, 'query.tab', defaultTabKey)
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
this.stopFetching()
|
this.stopFetching()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Vue from 'vue'
|
// eslint-disable-next-line no-unused
|
||||||
|
import { h } from 'vue'
|
||||||
import isEmpty from 'lodash/isEmpty'
|
import isEmpty from 'lodash/isEmpty'
|
||||||
import { getComponentProps } from '../../services/component_utils/component_utils'
|
import { getComponentProps } from '../../services/component_utils/component_utils'
|
||||||
import './with_load_more.scss'
|
import './with_load_more.scss'
|
||||||
|
|
@ -23,7 +24,7 @@ const withLoadMore = ({
|
||||||
const originalProps = Object.keys(getComponentProps(WrappedComponent))
|
const originalProps = Object.keys(getComponentProps(WrappedComponent))
|
||||||
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
|
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
|
||||||
|
|
||||||
return Vue.component('withLoadMore', {
|
return {
|
||||||
props,
|
props,
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|
@ -39,7 +40,7 @@ const withLoadMore = ({
|
||||||
this.fetchEntries()
|
this.fetchEntries()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed () {
|
unmounted () {
|
||||||
window.removeEventListener('scroll', this.scrollLoad)
|
window.removeEventListener('scroll', this.scrollLoad)
|
||||||
destroy && destroy(this.$props, this.$store)
|
destroy && destroy(this.$props, this.$store)
|
||||||
},
|
},
|
||||||
|
|
@ -79,16 +80,13 @@ const withLoadMore = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render (h) {
|
render () {
|
||||||
|
console.log(this.$listeners)
|
||||||
const props = {
|
const props = {
|
||||||
props: {
|
...this.$props,
|
||||||
...this.$props,
|
[childPropName]: this.entries
|
||||||
[childPropName]: this.entries
|
|
||||||
},
|
|
||||||
on: this.$listeners,
|
|
||||||
scopedSlots: this.$scopedSlots
|
|
||||||
}
|
}
|
||||||
const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
|
const children = this.$slots
|
||||||
return (
|
return (
|
||||||
<div class="with-load-more">
|
<div class="with-load-more">
|
||||||
<WrappedComponent {...props}>
|
<WrappedComponent {...props}>
|
||||||
|
|
@ -106,7 +104,7 @@ const withLoadMore = ({
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withLoadMore
|
export default withLoadMore
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Vue from 'vue'
|
// eslint-disable-next-line no-unused
|
||||||
|
import { h } from 'vue'
|
||||||
import isEmpty from 'lodash/isEmpty'
|
import isEmpty from 'lodash/isEmpty'
|
||||||
import { getComponentProps } from '../../services/component_utils/component_utils'
|
import { getComponentProps } from '../../services/component_utils/component_utils'
|
||||||
import './with_subscription.scss'
|
import './with_subscription.scss'
|
||||||
|
|
@ -22,7 +23,7 @@ const withSubscription = ({
|
||||||
const originalProps = Object.keys(getComponentProps(WrappedComponent))
|
const originalProps = Object.keys(getComponentProps(WrappedComponent))
|
||||||
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
|
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
|
||||||
|
|
||||||
return Vue.component('withSubscription', {
|
return {
|
||||||
props: [
|
props: [
|
||||||
...props,
|
...props,
|
||||||
'refresh' // boolean saying to force-fetch data whenever created
|
'refresh' // boolean saying to force-fetch data whenever created
|
||||||
|
|
@ -59,17 +60,13 @@ const withSubscription = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render (h) {
|
render () {
|
||||||
if (!this.error && !this.loading) {
|
if (!this.error && !this.loading) {
|
||||||
const props = {
|
const props = {
|
||||||
props: {
|
...this.$props,
|
||||||
...this.$props,
|
[childPropName]: this.fetchedData
|
||||||
[childPropName]: this.fetchedData
|
|
||||||
},
|
|
||||||
on: this.$listeners,
|
|
||||||
scopedSlots: this.$scopedSlots
|
|
||||||
}
|
}
|
||||||
const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
|
const children = this.$slots
|
||||||
return (
|
return (
|
||||||
<div class="with-subscription">
|
<div class="with-subscription">
|
||||||
<WrappedComponent {...props}>
|
<WrappedComponent {...props}>
|
||||||
|
|
@ -88,7 +85,7 @@ const withSubscription = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withSubscription
|
export default withSubscription
|
||||||
|
|
@ -85,7 +85,13 @@
|
||||||
},
|
},
|
||||||
"flash_content": "Click to show Flash content using Ruffle (Experimental, may not work).",
|
"flash_content": "Click to show Flash content using Ruffle (Experimental, may not work).",
|
||||||
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
|
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
|
||||||
"flash_fail": "Failed to load flash content, see console for details."
|
"flash_fail": "Failed to load flash content, see console for details.",
|
||||||
|
"scope_in_timeline": {
|
||||||
|
"direct": "Direct",
|
||||||
|
"private": "Followers-only",
|
||||||
|
"public": "Public",
|
||||||
|
"unlisted": "Unlisted"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"image_cropper": {
|
"image_cropper": {
|
||||||
"crop_picture": "Crop picture",
|
"crop_picture": "Crop picture",
|
||||||
|
|
@ -502,14 +508,14 @@
|
||||||
"true": "yes"
|
"true": "yes"
|
||||||
},
|
},
|
||||||
"virtual_scrolling": "Optimize timeline rendering",
|
"virtual_scrolling": "Optimize timeline rendering",
|
||||||
"use_at_icon": "Display @ symbol as an icon instead of text",
|
"use_at_icon": "Display {'@'} symbol as an icon instead of text",
|
||||||
"mention_link_display": "Display mention links",
|
"mention_link_display": "Display mention links",
|
||||||
"mention_link_display_short": "always as short names (e.g. @foo)",
|
"mention_link_display_short": "always as short names (e.g. {'@'}foo)",
|
||||||
"mention_link_display_full_for_remote": "as full names only for remote users (e.g. @foo@example.org)",
|
"mention_link_display_full_for_remote": "as full names only for remote users (e.g. {'@'}foo{'@'}example.org)",
|
||||||
"mention_link_display_full": "always as full names (e.g. @foo@example.org)",
|
"mention_link_display_full": "always as full names (e.g. {'@'}foo{'@'}example.org)",
|
||||||
"mention_link_show_tooltip": "Show full user names as tooltip for remote users",
|
"mention_link_show_tooltip": "Show full user names as tooltip for remote users",
|
||||||
"mention_link_show_avatar": "Show user avatar beside the link",
|
"mention_link_show_avatar": "Show user avatar beside the link",
|
||||||
"mention_link_fade_domain": "Fade domains (e.g. @example.org in @foo@example.org)",
|
"mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)",
|
||||||
"mention_link_bolden_you": "Highlight mention of you when you are mentioned",
|
"mention_link_bolden_you": "Highlight mention of you when you are mentioned",
|
||||||
"fun": "Fun",
|
"fun": "Fun",
|
||||||
"greentext": "Meme arrows",
|
"greentext": "Meme arrows",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import merge from 'lodash.merge'
|
import merge from 'lodash.merge'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { each, get, set } from 'lodash'
|
import { each, get, set, cloneDeep } from 'lodash'
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ export default function createPersistedState ({
|
||||||
subscriber(store)((mutation, state) => {
|
subscriber(store)((mutation, state) => {
|
||||||
try {
|
try {
|
||||||
if (saveImmedeatelyActions.includes(mutation.type)) {
|
if (saveImmedeatelyActions.includes(mutation.type)) {
|
||||||
setState(key, reducer(state, paths), storage)
|
setState(key, reducer(cloneDeep(state), paths), storage)
|
||||||
.then(success => {
|
.then(success => {
|
||||||
if (typeof success !== 'undefined') {
|
if (typeof success !== 'undefined') {
|
||||||
if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
|
if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
|
||||||
|
|
|
||||||
33
src/main.js
33
src/main.js
|
|
@ -1,6 +1,4 @@
|
||||||
import Vue from 'vue'
|
import { createStore } from 'vuex'
|
||||||
import VueRouter from 'vue-router'
|
|
||||||
import Vuex from 'vuex'
|
|
||||||
|
|
||||||
import 'custom-event-polyfill'
|
import 'custom-event-polyfill'
|
||||||
import './lib/event_target_polyfill.js'
|
import './lib/event_target_polyfill.js'
|
||||||
|
|
@ -22,36 +20,18 @@ import pollsModule from './modules/polls.js'
|
||||||
import postStatusModule from './modules/postStatus.js'
|
import postStatusModule from './modules/postStatus.js'
|
||||||
import chatsModule from './modules/chats.js'
|
import chatsModule from './modules/chats.js'
|
||||||
|
|
||||||
import VueI18n from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import createPersistedState from './lib/persisted_state.js'
|
import createPersistedState from './lib/persisted_state.js'
|
||||||
import pushNotifications from './lib/push_notifications_plugin.js'
|
import pushNotifications from './lib/push_notifications_plugin.js'
|
||||||
|
|
||||||
import messages from './i18n/messages.js'
|
import messages from './i18n/messages.js'
|
||||||
|
|
||||||
import VueClickOutside from 'v-click-outside'
|
|
||||||
import PortalVue from 'portal-vue'
|
|
||||||
import VBodyScrollLock from './directives/body_scroll_lock'
|
|
||||||
|
|
||||||
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
|
||||||
|
|
||||||
import afterStoreSetup from './boot/after_store.js'
|
import afterStoreSetup from './boot/after_store.js'
|
||||||
|
|
||||||
const currentLocale = (window.navigator.language || 'en').split('-')[0]
|
const currentLocale = (window.navigator.language || 'en').split('-')[0]
|
||||||
|
|
||||||
Vue.use(Vuex)
|
const i18n = createI18n({
|
||||||
Vue.use(VueRouter)
|
|
||||||
Vue.use(VueI18n)
|
|
||||||
Vue.use(VueClickOutside)
|
|
||||||
Vue.use(PortalVue)
|
|
||||||
Vue.use(VBodyScrollLock)
|
|
||||||
|
|
||||||
Vue.config.ignoredElements = ['pinch-zoom']
|
|
||||||
|
|
||||||
Vue.component('FAIcon', FontAwesomeIcon)
|
|
||||||
Vue.component('FALayers', FontAwesomeLayers)
|
|
||||||
|
|
||||||
const i18n = new VueI18n({
|
|
||||||
// By default, use the browser locale, we will update it if neccessary
|
// By default, use the browser locale, we will update it if neccessary
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
|
|
@ -78,17 +58,18 @@ const persistedStateOptions = {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
storageError = true
|
storageError = true
|
||||||
}
|
}
|
||||||
const store = new Vuex.Store({
|
const store = createStore({
|
||||||
modules: {
|
modules: {
|
||||||
i18n: {
|
i18n: {
|
||||||
getters: {
|
getters: {
|
||||||
i18n: () => i18n
|
i18n: () => i18n.global
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
interface: interfaceModule,
|
interface: interfaceModule,
|
||||||
instance: instanceModule,
|
instance: instanceModule,
|
||||||
statuses: statusesModule,
|
// TODO refactor users/statuses modules, they depend on each other
|
||||||
users: usersModule,
|
users: usersModule,
|
||||||
|
statuses: statusesModule,
|
||||||
api: apiModule,
|
api: apiModule,
|
||||||
config: configModule,
|
config: configModule,
|
||||||
serverSideConfig: serverSideConfigModule,
|
serverSideConfig: serverSideConfigModule,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Vue from 'vue'
|
import { reactive } from 'vue'
|
||||||
import { find, omitBy, orderBy, sumBy } from 'lodash'
|
import { find, omitBy, orderBy, sumBy } from 'lodash'
|
||||||
import chatService from '../services/chat_service/chat_service.js'
|
import chatService from '../services/chat_service/chat_service.js'
|
||||||
import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js'
|
import { parseChat, parseChatMessage } from '../services/entity_normalizer/entity_normalizer.service.js'
|
||||||
|
|
@ -13,8 +13,8 @@ const emptyChatList = () => ({
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
chatList: emptyChatList(),
|
chatList: emptyChatList(),
|
||||||
chatListFetcher: null,
|
chatListFetcher: null,
|
||||||
openedChats: {},
|
openedChats: reactive({}),
|
||||||
openedChatMessageServices: {},
|
openedChatMessageServices: reactive({}),
|
||||||
fetcher: undefined,
|
fetcher: undefined,
|
||||||
currentChatId: null,
|
currentChatId: null,
|
||||||
lastReadMessageId: null
|
lastReadMessageId: null
|
||||||
|
|
@ -137,10 +137,10 @@ const chats = {
|
||||||
},
|
},
|
||||||
addOpenedChat (state, { _dispatch, chat }) {
|
addOpenedChat (state, { _dispatch, chat }) {
|
||||||
state.currentChatId = chat.id
|
state.currentChatId = chat.id
|
||||||
Vue.set(state.openedChats, chat.id, chat)
|
state.openedChats[chat.id] = chat
|
||||||
|
|
||||||
if (!state.openedChatMessageServices[chat.id]) {
|
if (!state.openedChatMessageServices[chat.id]) {
|
||||||
Vue.set(state.openedChatMessageServices, chat.id, chatService.empty(chat.id))
|
state.openedChatMessageServices[chat.id] = chatService.empty(chat.id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setCurrentChatId (state, { chatId }) {
|
setCurrentChatId (state, { chatId }) {
|
||||||
|
|
@ -160,7 +160,7 @@ const chats = {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.chatList.data.push(updatedChat)
|
state.chatList.data.push(updatedChat)
|
||||||
Vue.set(state.chatList.idStore, updatedChat.id, updatedChat)
|
state.chatList.idStore[updatedChat.id] = updatedChat
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -172,7 +172,7 @@ const chats = {
|
||||||
chat.updated_at = updatedChat.updated_at
|
chat.updated_at = updatedChat.updated_at
|
||||||
}
|
}
|
||||||
if (!chat) { state.chatList.data.unshift(updatedChat) }
|
if (!chat) { state.chatList.data.unshift(updatedChat) }
|
||||||
Vue.set(state.chatList.idStore, updatedChat.id, updatedChat)
|
state.chatList.idStore[updatedChat.id] = updatedChat
|
||||||
},
|
},
|
||||||
deleteChat (state, { _dispatch, id, _rootGetters }) {
|
deleteChat (state, { _dispatch, id, _rootGetters }) {
|
||||||
state.chats.data = state.chats.data.filter(conversation =>
|
state.chats.data = state.chats.data.filter(conversation =>
|
||||||
|
|
@ -186,8 +186,8 @@ const chats = {
|
||||||
commit('setChatListFetcher', { fetcher: undefined })
|
commit('setChatListFetcher', { fetcher: undefined })
|
||||||
for (const chatId in state.openedChats) {
|
for (const chatId in state.openedChats) {
|
||||||
chatService.clear(state.openedChatMessageServices[chatId])
|
chatService.clear(state.openedChatMessageServices[chatId])
|
||||||
Vue.delete(state.openedChats, chatId)
|
delete state.openedChats[chatId]
|
||||||
Vue.delete(state.openedChatMessageServices, chatId)
|
delete state.openedChatMessageServices[chatId]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setChatsLoading (state, { value }) {
|
setChatsLoading (state, { value }) {
|
||||||
|
|
@ -215,8 +215,8 @@ const chats = {
|
||||||
for (const chatId in state.openedChats) {
|
for (const chatId in state.openedChats) {
|
||||||
if (currentChatId !== chatId) {
|
if (currentChatId !== chatId) {
|
||||||
chatService.clear(state.openedChatMessageServices[chatId])
|
chatService.clear(state.openedChatMessageServices[chatId])
|
||||||
Vue.delete(state.openedChats, chatId)
|
delete state.openedChats[chatId]
|
||||||
Vue.delete(state.openedChatMessageServices, chatId)
|
delete state.openedChatMessageServices[chatId]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { set, delete as del } from 'vue'
|
|
||||||
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
||||||
import messages from '../i18n/messages'
|
import messages from '../i18n/messages'
|
||||||
|
|
||||||
|
|
@ -122,14 +121,14 @@ const config = {
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setOption (state, { name, value }) {
|
setOption (state, { name, value }) {
|
||||||
set(state, name, value)
|
state[name] = value
|
||||||
},
|
},
|
||||||
setHighlight (state, { user, color, type }) {
|
setHighlight (state, { user, color, type }) {
|
||||||
const data = this.state.config.highlight[user]
|
const data = this.state.config.highlight[user]
|
||||||
if (color || type) {
|
if (color || type) {
|
||||||
set(state.highlight, user, { color: color || data.color, type: type || data.type })
|
state.highlight[user] = { color: color || data.color, type: type || data.type }
|
||||||
} else {
|
} else {
|
||||||
del(state.highlight, user)
|
delete state.highlight[user]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { set } from 'vue'
|
|
||||||
import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
||||||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
||||||
import apiService from '../services/api/api.service.js'
|
import apiService from '../services/api/api.service.js'
|
||||||
|
|
@ -102,7 +101,7 @@ const instance = {
|
||||||
mutations: {
|
mutations: {
|
||||||
setInstanceOption (state, { name, value }) {
|
setInstanceOption (state, { name, value }) {
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
set(state, name, value)
|
state[name] = value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setKnownDomains (state, domains) {
|
setKnownDomains (state, domains) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { set, delete as del } from 'vue'
|
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
settingsModalState: 'hidden',
|
settingsModalState: 'hidden',
|
||||||
settingsModalLoaded: false,
|
settingsModalLoaded: false,
|
||||||
|
|
@ -29,11 +27,10 @@ const interfaceMod = {
|
||||||
if (state.noticeClearTimeout) {
|
if (state.noticeClearTimeout) {
|
||||||
clearTimeout(state.noticeClearTimeout)
|
clearTimeout(state.noticeClearTimeout)
|
||||||
}
|
}
|
||||||
set(state.settings, 'currentSaveStateNotice', { error: false, data: success })
|
state.settings.currentSaveStateNotice = { error: false, data: success }
|
||||||
set(state.settings, 'noticeClearTimeout',
|
state.settings.noticeClearTimeout = setTimeout(() => delete state.settings.currentSaveStateNotice, 2000)
|
||||||
setTimeout(() => del(state.settings, 'currentSaveStateNotice'), 2000))
|
|
||||||
} else {
|
} else {
|
||||||
set(state.settings, 'currentSaveStateNotice', { error: true, errorData: error })
|
state.settings.currentSaveStateNotice = { error: true, errorData: error }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setNotificationPermission (state, permission) {
|
setNotificationPermission (state, permission) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { delete as del } from 'vue'
|
|
||||||
|
|
||||||
const oauth = {
|
const oauth = {
|
||||||
state: {
|
state: {
|
||||||
clientId: false,
|
clientId: false,
|
||||||
|
|
@ -29,7 +27,7 @@ const oauth = {
|
||||||
state.userToken = false
|
state.userToken = false
|
||||||
// state.token is userToken with older name, coming from persistent state
|
// state.token is userToken with older name, coming from persistent state
|
||||||
// let's clear it as well, since it is being used as a fallback of state.userToken
|
// let's clear it as well, since it is being used as a fallback of state.userToken
|
||||||
del(state, 'token')
|
delete state.token
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { merge } from 'lodash'
|
import { merge } from 'lodash'
|
||||||
import { set } from 'vue'
|
|
||||||
|
|
||||||
const polls = {
|
const polls = {
|
||||||
state: {
|
state: {
|
||||||
|
|
@ -13,25 +12,25 @@ const polls = {
|
||||||
// Make expired-state change trigger re-renders properly
|
// Make expired-state change trigger re-renders properly
|
||||||
poll.expired = Date.now() > Date.parse(poll.expires_at)
|
poll.expired = Date.now() > Date.parse(poll.expires_at)
|
||||||
if (existingPoll) {
|
if (existingPoll) {
|
||||||
set(state.pollsObject, poll.id, merge(existingPoll, poll))
|
state.pollsObject[poll.id] = merge(existingPoll, poll)
|
||||||
} else {
|
} else {
|
||||||
set(state.pollsObject, poll.id, poll)
|
state.pollsObject[poll.id] = poll
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trackPoll (state, pollId) {
|
trackPoll (state, pollId) {
|
||||||
const currentValue = state.trackedPolls[pollId]
|
const currentValue = state.trackedPolls[pollId]
|
||||||
if (currentValue) {
|
if (currentValue) {
|
||||||
set(state.trackedPolls, pollId, currentValue + 1)
|
state.trackedPolls[pollId] = currentValue + 1
|
||||||
} else {
|
} else {
|
||||||
set(state.trackedPolls, pollId, 1)
|
state.trackedPolls[pollId] = 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
untrackPoll (state, pollId) {
|
untrackPoll (state, pollId) {
|
||||||
const currentValue = state.trackedPolls[pollId]
|
const currentValue = state.trackedPolls[pollId]
|
||||||
if (currentValue) {
|
if (currentValue) {
|
||||||
set(state.trackedPolls, pollId, currentValue - 1)
|
state.trackedPolls[pollId] = currentValue - 1
|
||||||
} else {
|
} else {
|
||||||
set(state.trackedPolls, pollId, 0)
|
state.trackedPolls[pollId] = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
isArray,
|
isArray,
|
||||||
omitBy
|
omitBy
|
||||||
} from 'lodash'
|
} from 'lodash'
|
||||||
import { set } from 'vue'
|
|
||||||
import {
|
import {
|
||||||
isStatusNotification,
|
isStatusNotification,
|
||||||
isValidNotification,
|
isValidNotification,
|
||||||
|
|
@ -92,7 +91,7 @@ const mergeOrAdd = (arr, obj, item) => {
|
||||||
// This is a new item, prepare it
|
// This is a new item, prepare it
|
||||||
prepareStatus(item)
|
prepareStatus(item)
|
||||||
arr.push(item)
|
arr.push(item)
|
||||||
set(obj, item.id, item)
|
obj[item.id] = item
|
||||||
return { item, new: true }
|
return { item, new: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +130,7 @@ const addStatusToGlobalStorage = (state, data) => {
|
||||||
if (conversationsObject[conversationId]) {
|
if (conversationsObject[conversationId]) {
|
||||||
conversationsObject[conversationId].push(status)
|
conversationsObject[conversationId].push(status)
|
||||||
} else {
|
} else {
|
||||||
set(conversationsObject, conversationId, [status])
|
conversationsObject[conversationId] = [status]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
@ -523,7 +522,7 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) {
|
addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) {
|
||||||
const status = state.allStatusesObject[id]
|
const status = state.allStatusesObject[id]
|
||||||
set(status, 'emoji_reactions', emojiReactions)
|
status['emoji_reactions'] = emojiReactions
|
||||||
},
|
},
|
||||||
addOwnReaction (state, { id, emoji, currentUser }) {
|
addOwnReaction (state, { id, emoji, currentUser }) {
|
||||||
const status = state.allStatusesObject[id]
|
const status = state.allStatusesObject[id]
|
||||||
|
|
@ -542,9 +541,9 @@ export const mutations = {
|
||||||
|
|
||||||
// Update count of existing reaction if it exists, otherwise append at the end
|
// Update count of existing reaction if it exists, otherwise append at the end
|
||||||
if (reactionIndex >= 0) {
|
if (reactionIndex >= 0) {
|
||||||
set(status.emoji_reactions, reactionIndex, newReaction)
|
status.emoji_reactions[reactionIndex] = newReaction
|
||||||
} else {
|
} else {
|
||||||
set(status, 'emoji_reactions', [...status.emoji_reactions, newReaction])
|
status['emoji_reactions'] = [...status.emoji_reactions, newReaction]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeOwnReaction (state, { id, emoji, currentUser }) {
|
removeOwnReaction (state, { id, emoji, currentUser }) {
|
||||||
|
|
@ -563,9 +562,9 @@ export const mutations = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newReaction.count > 0) {
|
if (newReaction.count > 0) {
|
||||||
set(status.emoji_reactions, reactionIndex, newReaction)
|
status.emoji_reactions[reactionIndex] = newReaction
|
||||||
} else {
|
} else {
|
||||||
set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji))
|
status['emoji_reactions'] = status.emoji_reactions.filter(r => r.name !== emoji)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateStatusWithPoll (state, { id, poll }) {
|
updateStatusWithPoll (state, { id, poll }) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import oauthApi from '../services/new_api/oauth.js'
|
import oauthApi from '../services/new_api/oauth.js'
|
||||||
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
|
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
|
||||||
import { set } from 'vue'
|
|
||||||
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
|
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
|
||||||
|
|
||||||
// TODO: Unify with mergeOrAdd in statuses.js
|
// TODO: Unify with mergeOrAdd in statuses.js
|
||||||
|
|
@ -15,9 +14,9 @@ export const mergeOrAdd = (arr, obj, item) => {
|
||||||
} else {
|
} else {
|
||||||
// This is a new item, prepare it
|
// This is a new item, prepare it
|
||||||
arr.push(item)
|
arr.push(item)
|
||||||
set(obj, item.id, item)
|
obj[item.id] = item
|
||||||
if (item.screen_name && !item.screen_name.includes('@')) {
|
if (item.screen_name && !item.screen_name.includes('@')) {
|
||||||
set(obj, item.screen_name.toLowerCase(), item)
|
obj[item.screen_name.toLowerCase()] = item
|
||||||
}
|
}
|
||||||
return { item, new: true }
|
return { item, new: true }
|
||||||
}
|
}
|
||||||
|
|
@ -103,23 +102,23 @@ export const mutations = {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
const tags = user.tags || []
|
const tags = user.tags || []
|
||||||
const newTags = tags.concat([tag])
|
const newTags = tags.concat([tag])
|
||||||
set(user, 'tags', newTags)
|
user['tags'] = newTags
|
||||||
},
|
},
|
||||||
untagUser (state, { user: { id }, tag }) {
|
untagUser (state, { user: { id }, tag }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
const tags = user.tags || []
|
const tags = user.tags || []
|
||||||
const newTags = tags.filter(t => t !== tag)
|
const newTags = tags.filter(t => t !== tag)
|
||||||
set(user, 'tags', newTags)
|
user['tags'] = newTags
|
||||||
},
|
},
|
||||||
updateRight (state, { user: { id }, right, value }) {
|
updateRight (state, { user: { id }, right, value }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
let newRights = user.rights
|
let newRights = user.rights
|
||||||
newRights[right] = value
|
newRights[right] = value
|
||||||
set(user, 'rights', newRights)
|
user['rights'] = newRights
|
||||||
},
|
},
|
||||||
updateActivationStatus (state, { user: { id }, deactivated }) {
|
updateActivationStatus (state, { user: { id }, deactivated }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
set(user, 'deactivated', deactivated)
|
user['deactivated'] = deactivated
|
||||||
},
|
},
|
||||||
setCurrentUser (state, user) {
|
setCurrentUser (state, user) {
|
||||||
state.lastLoginName = user.screen_name
|
state.lastLoginName = user.screen_name
|
||||||
|
|
@ -148,26 +147,26 @@ export const mutations = {
|
||||||
clearFriends (state, userId) {
|
clearFriends (state, userId) {
|
||||||
const user = state.usersObject[userId]
|
const user = state.usersObject[userId]
|
||||||
if (user) {
|
if (user) {
|
||||||
set(user, 'friendIds', [])
|
user['friendIds'] = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearFollowers (state, userId) {
|
clearFollowers (state, userId) {
|
||||||
const user = state.usersObject[userId]
|
const user = state.usersObject[userId]
|
||||||
if (user) {
|
if (user) {
|
||||||
set(user, 'followerIds', [])
|
user['followerIds'] = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addNewUsers (state, users) {
|
addNewUsers (state, users) {
|
||||||
each(users, (user) => {
|
each(users, (user) => {
|
||||||
if (user.relationship) {
|
if (user.relationship) {
|
||||||
set(state.relationships, user.relationship.id, user.relationship)
|
state.relationships[user.relationship.id] = user.relationship
|
||||||
}
|
}
|
||||||
mergeOrAdd(state.users, state.usersObject, user)
|
mergeOrAdd(state.users, state.usersObject, user)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateUserRelationship (state, relationships) {
|
updateUserRelationship (state, relationships) {
|
||||||
relationships.forEach((relationship) => {
|
relationships.forEach((relationship) => {
|
||||||
set(state.relationships, relationship.id, relationship)
|
state.relationships[relationship.id] = relationship
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
saveBlockIds (state, blockIds) {
|
saveBlockIds (state, blockIds) {
|
||||||
|
|
@ -222,7 +221,7 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
setColor (state, { user: { id }, highlighted }) {
|
setColor (state, { user: { id }, highlighted }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
set(user, 'highlight', highlighted)
|
user['highlight'] = highlighted
|
||||||
},
|
},
|
||||||
signUpPending (state) {
|
signUpPending (state) {
|
||||||
state.signUpPending = true
|
state.signUpPending = true
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Vue from 'vue'
|
// TODO investigate if even necessary since VUE3
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
/* By default async components don't have any way to recover, if component is
|
/* By default async components don't have any way to recover, if component is
|
||||||
* failed, it is failed forever. This helper tries to remedy that by recreating
|
* failed, it is failed forever. This helper tries to remedy that by recreating
|
||||||
|
|
@ -13,7 +14,7 @@ function getResettableAsyncComponent (asyncComponent, options) {
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
|
|
||||||
const observe = Vue.observable({ c: asyncComponentFactory() })
|
const observe = reactive({ c: asyncComponentFactory() })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
functional: true,
|
functional: true,
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@
|
||||||
import localForage from 'localforage'
|
import localForage from 'localforage'
|
||||||
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
|
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
|
||||||
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
|
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
|
||||||
import Vue from 'vue'
|
import { createI18n } from 'vue-i18n'
|
||||||
import VueI18n from 'vue-i18n'
|
|
||||||
import messages from './i18n/service_worker_messages.js'
|
import messages from './i18n/service_worker_messages.js'
|
||||||
|
|
||||||
Vue.use(VueI18n)
|
const i18n = createI18n({
|
||||||
const i18n = new VueI18n({
|
|
||||||
// By default, use the browser locale, we will update it if neccessary
|
// By default, use the browser locale, we will update it if neccessary
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
import { configureCompat } from 'vue'
|
||||||
|
// disable compat for certain features
|
||||||
|
configureCompat({
|
||||||
|
COMPONENT_V_MODEL: false,
|
||||||
|
INSTANCE_SET: false,
|
||||||
|
RENDER_FUNCTION: false
|
||||||
|
})
|
||||||
// require all test files (files that ends with .spec.js)
|
// require all test files (files that ends with .spec.js)
|
||||||
const testsContext = require.context('./specs', true, /\.spec$/)
|
const testsContext = require.context('./specs', true, /\.spec$/)
|
||||||
testsContext.keys().forEach(testsContext)
|
testsContext.keys().forEach(testsContext)
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,40 @@
|
||||||
import Vuex from 'vuex'
|
|
||||||
import routes from 'src/boot/routes'
|
import routes from 'src/boot/routes'
|
||||||
import { createLocalVue } from '@vue/test-utils'
|
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||||
import VueRouter from 'vue-router'
|
import { createStore } from 'vuex'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const store = createStore({
|
||||||
localVue.use(Vuex)
|
|
||||||
localVue.use(VueRouter)
|
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
|
||||||
state: {
|
state: {
|
||||||
instance: {}
|
instance: {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('routes', () => {
|
describe('routes', () => {
|
||||||
const router = new VueRouter({
|
const router = createRouter({
|
||||||
mode: 'abstract',
|
history: createMemoryHistory(),
|
||||||
routes: routes(store)
|
routes: routes(store)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('root path', () => {
|
it('root path', async () => {
|
||||||
router.push('/main/all')
|
await router.push('/main/all')
|
||||||
|
|
||||||
const matchedComponents = router.getMatchedComponents()
|
const matchedComponents = router.currentRoute.value.matched
|
||||||
|
|
||||||
expect(matchedComponents[0].components.hasOwnProperty('Timeline')).to.eql(true)
|
expect(matchedComponents[0].components.default.components.hasOwnProperty('Timeline')).to.eql(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('user\'s profile', () => {
|
it('user\'s profile', async () => {
|
||||||
router.push('/fake-user-name')
|
await router.push('/fake-user-name')
|
||||||
|
|
||||||
const matchedComponents = router.getMatchedComponents()
|
const matchedComponents = router.currentRoute.value.matched
|
||||||
|
|
||||||
expect(matchedComponents[0].components.hasOwnProperty('UserCard')).to.eql(true)
|
expect(matchedComponents[0].components.default.components.hasOwnProperty('UserCard')).to.eql(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('user\'s profile at /users', () => {
|
it('user\'s profile at /users', async () => {
|
||||||
router.push('/users/fake-user-name')
|
await router.push('/users/fake-user-name')
|
||||||
|
|
||||||
const matchedComponents = router.getMatchedComponents()
|
const matchedComponents = router.currentRoute.value.matched
|
||||||
|
|
||||||
expect(matchedComponents[0].components.hasOwnProperty('UserCard')).to.eql(true)
|
expect(matchedComponents[0].components.default.components.hasOwnProperty('UserCard')).to.eql(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,108 +1,116 @@
|
||||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
import { h } from 'vue'
|
||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
|
import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
|
||||||
|
import vClickOutside from 'click-outside-vue3'
|
||||||
|
|
||||||
const generateInput = (value, padEmoji = true) => {
|
const generateInput = (value, padEmoji = true) => {
|
||||||
const localVue = createLocalVue()
|
|
||||||
localVue.directive('click-outside', () => {})
|
|
||||||
const wrapper = shallowMount(EmojiInput, {
|
const wrapper = shallowMount(EmojiInput, {
|
||||||
propsData: {
|
global: {
|
||||||
suggest: () => [],
|
renderStubDefaultSlot: true,
|
||||||
enableEmojiPicker: true,
|
mocks: {
|
||||||
value
|
$store: {
|
||||||
},
|
getters: {
|
||||||
mocks: {
|
mergedConfig: {
|
||||||
$store: {
|
padEmoji
|
||||||
getters: {
|
}
|
||||||
mergedConfig: {
|
|
||||||
padEmoji
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
stubs: {
|
||||||
|
FAIcon: true
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
'click-outside': vClickOutside
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
slots: {
|
props: {
|
||||||
default: '<input />'
|
suggest: () => [],
|
||||||
|
enableEmojiPicker: true,
|
||||||
|
modelValue: value
|
||||||
},
|
},
|
||||||
localVue
|
slots: {
|
||||||
|
'default': () => h('input', '')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return [wrapper, localVue]
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('EmojiInput', () => {
|
describe('EmojiInput', () => {
|
||||||
describe('insertion mechanism', () => {
|
describe('insertion mechanism', () => {
|
||||||
it('inserts string at the end with trailing space', () => {
|
it('inserts string at the end with trailing space', () => {
|
||||||
const initialString = 'Testing'
|
const initialString = 'Testing'
|
||||||
const [wrapper] = generateInput(initialString)
|
const wrapper = generateInput(initialString)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: initialString.length })
|
wrapper.setData({ caret: initialString.length })
|
||||||
wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
|
wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
|
||||||
const inputEvents = wrapper.emitted().input
|
const inputEvents = wrapper.emitted()['update:modelValue']
|
||||||
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ')
|
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('inserts string at the end with trailing space (source has a trailing space)', () => {
|
it('inserts string at the end with trailing space (source has a trailing space)', () => {
|
||||||
const initialString = 'Testing '
|
const initialString = 'Testing '
|
||||||
const [wrapper] = generateInput(initialString)
|
const wrapper = generateInput(initialString)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: initialString.length })
|
wrapper.setData({ caret: initialString.length })
|
||||||
wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
|
wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
|
||||||
const inputEvents = wrapper.emitted().input
|
const inputEvents = wrapper.emitted()['update:modelValue']
|
||||||
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ')
|
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Testing (test) ')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('inserts string at the begginning without leading space', () => {
|
it('inserts string at the begginning without leading space', () => {
|
||||||
const initialString = 'Testing'
|
const initialString = 'Testing'
|
||||||
const [wrapper] = generateInput(initialString)
|
const wrapper = generateInput(initialString)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: 0 })
|
wrapper.setData({ caret: 0 })
|
||||||
wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
|
wrapper.vm.insert({ insertion: '(test)', keepOpen: false })
|
||||||
const inputEvents = wrapper.emitted().input
|
const inputEvents = wrapper.emitted()['update:modelValue']
|
||||||
expect(inputEvents[inputEvents.length - 1][0]).to.eql('(test) Testing')
|
expect(inputEvents[inputEvents.length - 1][0]).to.eql('(test) Testing')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('inserts string between words without creating extra spaces', () => {
|
it('inserts string between words without creating extra spaces', () => {
|
||||||
const initialString = 'Spurdo Sparde'
|
const initialString = 'Spurdo Sparde'
|
||||||
const [wrapper] = generateInput(initialString)
|
const wrapper = generateInput(initialString)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: 6 })
|
wrapper.setData({ caret: 6 })
|
||||||
wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false })
|
wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false })
|
||||||
const inputEvents = wrapper.emitted().input
|
const inputEvents = wrapper.emitted()['update:modelValue']
|
||||||
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde')
|
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('inserts string between words without creating extra spaces (other caret)', () => {
|
it('inserts string between words without creating extra spaces (other caret)', () => {
|
||||||
const initialString = 'Spurdo Sparde'
|
const initialString = 'Spurdo Sparde'
|
||||||
const [wrapper] = generateInput(initialString)
|
const wrapper = generateInput(initialString)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: 7 })
|
wrapper.setData({ caret: 7 })
|
||||||
wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false })
|
wrapper.vm.insert({ insertion: ':ebin:', keepOpen: false })
|
||||||
const inputEvents = wrapper.emitted().input
|
const inputEvents = wrapper.emitted()['update:modelValue']
|
||||||
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde')
|
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Spurdo :ebin: Sparde')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('inserts string without any padding if padEmoji setting is set to false', () => {
|
it('inserts string without any padding if padEmoji setting is set to false', () => {
|
||||||
const initialString = 'Eat some spam!'
|
const initialString = 'Eat some spam!'
|
||||||
const [wrapper] = generateInput(initialString, false)
|
const wrapper = generateInput(initialString, false)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: initialString.length, keepOpen: false })
|
wrapper.setData({ caret: initialString.length, keepOpen: false })
|
||||||
wrapper.vm.insert({ insertion: ':spam:' })
|
wrapper.vm.insert({ insertion: ':spam:' })
|
||||||
const inputEvents = wrapper.emitted().input
|
const inputEvents = wrapper.emitted()['update:modelValue']
|
||||||
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Eat some spam!:spam:')
|
expect(inputEvents[inputEvents.length - 1][0]).to.eql('Eat some spam!:spam:')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('correctly sets caret after insertion at beginning', (done) => {
|
it('correctly sets caret after insertion at beginning', (done) => {
|
||||||
const initialString = '1234'
|
const initialString = '1234'
|
||||||
const [wrapper, vue] = generateInput(initialString)
|
const wrapper = generateInput(initialString)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: 0 })
|
wrapper.setData({ caret: 0 })
|
||||||
wrapper.vm.insert({ insertion: '1234', keepOpen: false })
|
wrapper.vm.insert({ insertion: '1234', keepOpen: false })
|
||||||
vue.nextTick(() => {
|
wrapper.vm.$nextTick(() => {
|
||||||
expect(wrapper.vm.caret).to.eql(5)
|
expect(wrapper.vm.caret).to.eql(5)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
@ -110,12 +118,12 @@ describe('EmojiInput', () => {
|
||||||
|
|
||||||
it('correctly sets caret after insertion at end', (done) => {
|
it('correctly sets caret after insertion at end', (done) => {
|
||||||
const initialString = '1234'
|
const initialString = '1234'
|
||||||
const [wrapper, vue] = generateInput(initialString)
|
const wrapper = generateInput(initialString)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: initialString.length })
|
wrapper.setData({ caret: initialString.length })
|
||||||
wrapper.vm.insert({ insertion: '1234', keepOpen: false })
|
wrapper.vm.insert({ insertion: '1234', keepOpen: false })
|
||||||
vue.nextTick(() => {
|
wrapper.vm.$nextTick(() => {
|
||||||
expect(wrapper.vm.caret).to.eql(10)
|
expect(wrapper.vm.caret).to.eql(10)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
@ -123,12 +131,12 @@ describe('EmojiInput', () => {
|
||||||
|
|
||||||
it('correctly sets caret after insertion if padEmoji setting is set to false', (done) => {
|
it('correctly sets caret after insertion if padEmoji setting is set to false', (done) => {
|
||||||
const initialString = '1234'
|
const initialString = '1234'
|
||||||
const [wrapper, vue] = generateInput(initialString, false)
|
const wrapper = generateInput(initialString, false)
|
||||||
const input = wrapper.find('input')
|
const input = wrapper.find('input')
|
||||||
input.setValue(initialString)
|
input.setValue(initialString)
|
||||||
wrapper.setData({ caret: initialString.length })
|
wrapper.setData({ caret: initialString.length })
|
||||||
wrapper.vm.insert({ insertion: '1234', keepOpen: false })
|
wrapper.vm.insert({ insertion: '1234', keepOpen: false })
|
||||||
vue.nextTick(() => {
|
wrapper.vm.$nextTick(() => {
|
||||||
expect(wrapper.vm.caret).to.eql(8)
|
expect(wrapper.vm.caret).to.eql(8)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
|
import { mount, shallowMount } from '@vue/test-utils'
|
||||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
|
||||||
const attentions = []
|
const attentions = []
|
||||||
|
const global = {
|
||||||
|
mocks: {
|
||||||
|
'$store': null
|
||||||
|
},
|
||||||
|
stubs: {
|
||||||
|
FAIcon: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const makeMention = (who) => {
|
const makeMention = (who) => {
|
||||||
attentions.push({ statusnet_profile_url: `https://fake.tld/@${who}` })
|
attentions.push({ statusnet_profile_url: `https://fake.tld/@${who}` })
|
||||||
|
|
@ -11,17 +18,17 @@ const makeMention = (who) => {
|
||||||
const p = (...data) => `<p>${data.join('')}</p>`
|
const p = (...data) => `<p>${data.join('')}</p>`
|
||||||
const compwrap = (...data) => `<span class="RichContent">${data.join('')}</span>`
|
const compwrap = (...data) => `<span class="RichContent">${data.join('')}</span>`
|
||||||
const mentionsLine = (times) => [
|
const mentionsLine = (times) => [
|
||||||
'<mentionsline-stub mentions="',
|
'<mentions-line-stub mentions="',
|
||||||
new Array(times).fill('[object Object]').join(','),
|
new Array(times).fill('[object Object]').join(','),
|
||||||
'"></mentionsline-stub>'
|
'"></mentions-line-stub>'
|
||||||
].join('')
|
].join('')
|
||||||
|
|
||||||
describe('RichContent', () => {
|
describe('RichContent', () => {
|
||||||
it('renders simple post without exploding', () => {
|
it('renders simple post without exploding', () => {
|
||||||
const html = p('Hello world!')
|
const html = p('Hello world!')
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -30,7 +37,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(html))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(html))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('unescapes everything as needed', () => {
|
it('unescapes everything as needed', () => {
|
||||||
|
|
@ -43,8 +50,8 @@ describe('RichContent', () => {
|
||||||
'Testing \'em all'
|
'Testing \'em all'
|
||||||
].join('')
|
].join('')
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -53,7 +60,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('replaces mention with mentionsline', () => {
|
it('replaces mention with mentionsline', () => {
|
||||||
|
|
@ -62,8 +69,8 @@ describe('RichContent', () => {
|
||||||
' how are you doing today?'
|
' how are you doing today?'
|
||||||
)
|
)
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -72,7 +79,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(p(
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(p(
|
||||||
mentionsLine(1),
|
mentionsLine(1),
|
||||||
' how are you doing today?'
|
' how are you doing today?'
|
||||||
)))
|
)))
|
||||||
|
|
@ -93,17 +100,17 @@ describe('RichContent', () => {
|
||||||
),
|
),
|
||||||
// TODO fix this extra line somehow?
|
// TODO fix this extra line somehow?
|
||||||
p(
|
p(
|
||||||
'<mentionsline-stub mentions="',
|
'<mentions-line-stub mentions="',
|
||||||
'[object Object],',
|
'[object Object],',
|
||||||
'[object Object],',
|
'[object Object],',
|
||||||
'[object Object]',
|
'[object Object]',
|
||||||
'"></mentionsline-stub>'
|
'"></mentions-line-stub>'
|
||||||
)
|
)
|
||||||
].join('')
|
].join('')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -112,7 +119,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Does not touch links if link handling is disabled', () => {
|
it('Does not touch links if link handling is disabled', () => {
|
||||||
|
|
@ -130,8 +137,8 @@ describe('RichContent', () => {
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: false,
|
handleLinks: false,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -154,8 +161,8 @@ describe('RichContent', () => {
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: false,
|
handleLinks: false,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -174,8 +181,8 @@ describe('RichContent', () => {
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: false,
|
handleLinks: false,
|
||||||
greentext: false,
|
greentext: false,
|
||||||
|
|
@ -191,12 +198,12 @@ describe('RichContent', () => {
|
||||||
const html = p('Ebin :DDDD :spurdo:')
|
const html = p('Ebin :DDDD :spurdo:')
|
||||||
const expected = p(
|
const expected = p(
|
||||||
'Ebin :DDDD ',
|
'Ebin :DDDD ',
|
||||||
'<anonymous-stub alt=":spurdo:" src="about:blank" title=":spurdo:" class="emoji img"></anonymous-stub>'
|
'<anonymous-stub src="about:blank" alt=":spurdo:" class="emoji img" title=":spurdo:"></anonymous-stub>'
|
||||||
)
|
)
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: false,
|
handleLinks: false,
|
||||||
greentext: false,
|
greentext: false,
|
||||||
|
|
@ -205,15 +212,15 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Doesn\'t add nonexistent emoji to post', () => {
|
it('Doesn\'t add nonexistent emoji to post', () => {
|
||||||
const html = p('Lol :lol:')
|
const html = p('Lol :lol:')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: false,
|
handleLinks: false,
|
||||||
greentext: false,
|
greentext: false,
|
||||||
|
|
@ -222,7 +229,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(html))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(html))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Greentext + last mentions', () => {
|
it('Greentext + last mentions', () => {
|
||||||
|
|
@ -240,8 +247,8 @@ describe('RichContent', () => {
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -272,8 +279,8 @@ describe('RichContent', () => {
|
||||||
].join('<br>')
|
].join('<br>')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -282,7 +289,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('buggy example/hashtags', () => {
|
it('buggy example/hashtags', () => {
|
||||||
|
|
@ -300,16 +307,18 @@ describe('RichContent', () => {
|
||||||
'<p>',
|
'<p>',
|
||||||
'<a href="http://macrochan.org/images/N/H/NHCMDUXJPPZ6M3Z2CQ6D2EBRSWGE7MZY.jpg" target="_blank">',
|
'<a href="http://macrochan.org/images/N/H/NHCMDUXJPPZ6M3Z2CQ6D2EBRSWGE7MZY.jpg" target="_blank">',
|
||||||
'NHCMDUXJPPZ6M3Z2CQ6D2EBRSWGE7MZY.jpg</a>',
|
'NHCMDUXJPPZ6M3Z2CQ6D2EBRSWGE7MZY.jpg</a>',
|
||||||
' <hashtaglink-stub url="https://shitposter.club/tag/nou" content="#nou" tag="nou">',
|
' <hashtag-link-stub url="https://shitposter.club/tag/nou" content="#nou" tag="nou">',
|
||||||
'</hashtaglink-stub>',
|
'#nou',
|
||||||
' <hashtaglink-stub url="https://shitposter.club/tag/screencap" content="#screencap" tag="screencap">',
|
'</hashtag-link-stub>',
|
||||||
'</hashtaglink-stub>',
|
' <hashtag-link-stub url="https://shitposter.club/tag/screencap" content="#screencap" tag="screencap">',
|
||||||
|
'#screencap',
|
||||||
|
'</hashtag-link-stub>',
|
||||||
' </p>'
|
' </p>'
|
||||||
].join('')
|
].join('')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -318,7 +327,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rich contents of a mention are handled properly', () => {
|
it('rich contents of a mention are handled properly', () => {
|
||||||
|
|
@ -342,7 +351,8 @@ describe('RichContent', () => {
|
||||||
p(
|
p(
|
||||||
'<span class="MentionsLine">',
|
'<span class="MentionsLine">',
|
||||||
'<span class="MentionLink mention-link">',
|
'<span class="MentionLink mention-link">',
|
||||||
'<a href="lol" target="_blank" class="original">',
|
'<!-- eslint-disable vue/no-v-html -->',
|
||||||
|
'<a href="lol" class="original" target="_blank">',
|
||||||
'<span>',
|
'<span>',
|
||||||
'https://</span>',
|
'https://</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
|
|
@ -350,9 +360,10 @@ describe('RichContent', () => {
|
||||||
'<span>',
|
'<span>',
|
||||||
'</span>',
|
'</span>',
|
||||||
'</a>',
|
'</a>',
|
||||||
'<!---->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
|
'<!-- eslint-enable vue/no-v-html -->',
|
||||||
|
'<!--v-if-->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
|
||||||
'</span>',
|
'</span>',
|
||||||
'<!---->', // v-if placeholder, mentionsline's extra mentions and stuff
|
'<!--v-if-->', // v-if placeholder, mentionsline's extra mentions and stuff
|
||||||
'</span>'
|
'</span>'
|
||||||
),
|
),
|
||||||
p(
|
p(
|
||||||
|
|
@ -361,8 +372,8 @@ describe('RichContent', () => {
|
||||||
].join('')
|
].join('')
|
||||||
|
|
||||||
const wrapper = mount(RichContent, {
|
const wrapper = mount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -371,76 +382,73 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rich contents of nested mentions are handled properly', () => {
|
it('rich contents of nested mentions are handled properly', () => {
|
||||||
attentions.push({ statusnet_profile_url: 'lol' })
|
attentions.push({ statusnet_profile_url: 'lol' })
|
||||||
const html = [
|
const html = [
|
||||||
p(
|
'<span class="poast-style">',
|
||||||
'<span class="poast-style">',
|
'<a href="lol" class="mention">',
|
||||||
'<a href="lol" class="mention">',
|
'<span>',
|
||||||
'<span>',
|
'https://</span>',
|
||||||
'https://</span>',
|
'<span>',
|
||||||
'<span>',
|
'lol.tld/</span>',
|
||||||
'lol.tld/</span>',
|
'<span>',
|
||||||
'<span>',
|
'</span>',
|
||||||
'</span>',
|
'</a>',
|
||||||
'</a>',
|
' ',
|
||||||
' ',
|
'<a href="lol" class="mention">',
|
||||||
'<a href="lol" class="mention">',
|
'<span>',
|
||||||
'<span>',
|
'https://</span>',
|
||||||
'https://</span>',
|
'<span>',
|
||||||
'<span>',
|
'lol.tld/</span>',
|
||||||
'lol.tld/</span>',
|
'<span>',
|
||||||
'<span>',
|
'</span>',
|
||||||
'</span>',
|
'</a>',
|
||||||
'</a>',
|
' ',
|
||||||
'</span>'
|
'</span>',
|
||||||
),
|
'Testing'
|
||||||
p(
|
|
||||||
'Testing'
|
|
||||||
)
|
|
||||||
].join('')
|
].join('')
|
||||||
const expected = [
|
const expected = [
|
||||||
p(
|
'<span class="poast-style">',
|
||||||
'<span class="poast-style">',
|
'<span class="MentionsLine">',
|
||||||
'<span class="MentionsLine">',
|
'<span class="MentionLink mention-link">',
|
||||||
'<span class="MentionLink mention-link">',
|
'<!-- eslint-disable vue/no-v-html -->',
|
||||||
'<a href="lol" target="_blank" class="original">',
|
'<a href="lol" class="original" target="_blank">',
|
||||||
'<span>',
|
'<span>',
|
||||||
'https://</span>',
|
'https://</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'lol.tld/</span>',
|
'lol.tld/</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'</span>',
|
'</span>',
|
||||||
'</a>',
|
'</a>',
|
||||||
'<!---->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
|
'<!-- eslint-enable vue/no-v-html -->',
|
||||||
'</span>',
|
'<!--v-if-->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
|
||||||
'<span class="MentionLink mention-link">',
|
'</span>',
|
||||||
'<a href="lol" target="_blank" class="original">',
|
'<span class="MentionLink mention-link">',
|
||||||
'<span>',
|
'<!-- eslint-disable vue/no-v-html -->',
|
||||||
'https://</span>',
|
'<a href="lol" class="original" target="_blank">',
|
||||||
'<span>',
|
'<span>',
|
||||||
'lol.tld/</span>',
|
'https://</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'</span>',
|
'lol.tld/</span>',
|
||||||
'</a>',
|
'<span>',
|
||||||
'<!---->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
|
'</span>',
|
||||||
'</span>',
|
'</a>',
|
||||||
'<!---->', // v-if placeholder, mentionsline's extra mentions and stuff
|
'<!-- eslint-enable vue/no-v-html -->',
|
||||||
'</span>',
|
'<!--v-if-->', // v-if placeholder, mentionlink's "new" (i.e. rich) display
|
||||||
'</span>'
|
'</span>',
|
||||||
),
|
'<!--v-if-->', // v-if placeholder, mentionsline's extra mentions and stuff
|
||||||
|
'</span>',
|
||||||
' ',
|
' ',
|
||||||
p(
|
'</span>',
|
||||||
'Testing'
|
'Testing'
|
||||||
)
|
|
||||||
].join('')
|
].join('')
|
||||||
|
|
||||||
const wrapper = mount(RichContent, {
|
const wrapper = mount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -449,7 +457,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rich contents of a link are handled properly', () => {
|
it('rich contents of a link are handled properly', () => {
|
||||||
|
|
@ -483,8 +491,8 @@ describe('RichContent', () => {
|
||||||
].join('')
|
].join('')
|
||||||
|
|
||||||
const wrapper = shallowMount(RichContent, {
|
const wrapper = shallowMount(RichContent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks: true,
|
handleLinks: true,
|
||||||
greentext: true,
|
greentext: true,
|
||||||
|
|
@ -493,7 +501,7 @@ describe('RichContent', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.html()).to.eql(compwrap(expected))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('[INFORMATIVE] Performance testing, 10 000 simple posts', () => {
|
it.skip('[INFORMATIVE] Performance testing, 10 000 simple posts', () => {
|
||||||
|
|
@ -530,8 +538,8 @@ describe('RichContent', () => {
|
||||||
const t0 = performance.now()
|
const t0 = performance.now()
|
||||||
|
|
||||||
const wrapper = mount(TestComponent, {
|
const wrapper = mount(TestComponent, {
|
||||||
localVue,
|
global,
|
||||||
propsData: {
|
props: {
|
||||||
attentions,
|
attentions,
|
||||||
handleLinks,
|
handleLinks,
|
||||||
vhtml
|
vhtml
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { mount, createLocalVue } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import Vuex from 'vuex'
|
import { createStore } from 'vuex'
|
||||||
import UserProfile from 'src/components/user_profile/user_profile.vue'
|
import UserProfile from 'src/components/user_profile/user_profile.vue'
|
||||||
import backendInteractorService from 'src/services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from 'src/services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { getters } from 'src/modules/users.js'
|
import { getters } from 'src/modules/users.js'
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
|
||||||
localVue.use(Vuex)
|
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
clearTimeline: () => {}
|
clearTimeline: () => {}
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +39,7 @@ const extUser = {
|
||||||
screen_name_ui: 'testUser@test.instance'
|
screen_name_ui: 'testUser@test.instance'
|
||||||
}
|
}
|
||||||
|
|
||||||
const externalProfileStore = new Vuex.Store({
|
const externalProfileStore = createStore({
|
||||||
mutations,
|
mutations,
|
||||||
actions,
|
actions,
|
||||||
getters: testGetters,
|
getters: testGetters,
|
||||||
|
|
@ -104,7 +101,7 @@ const externalProfileStore = new Vuex.Store({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const localProfileStore = new Vuex.Store({
|
const localProfileStore = createStore({
|
||||||
mutations,
|
mutations,
|
||||||
actions,
|
actions,
|
||||||
getters: testGetters,
|
getters: testGetters,
|
||||||
|
|
@ -173,17 +170,19 @@ const localProfileStore = new Vuex.Store({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('UserProfile', () => {
|
// https://github.com/vuejs/test-utils/issues/1382
|
||||||
|
describe.skip('UserProfile', () => {
|
||||||
it('renders external profile', () => {
|
it('renders external profile', () => {
|
||||||
const wrapper = mount(UserProfile, {
|
const wrapper = mount(UserProfile, {
|
||||||
localVue,
|
global: {
|
||||||
store: externalProfileStore,
|
plugins: [ externalProfileStore ],
|
||||||
mocks: {
|
mocks: {
|
||||||
$route: {
|
$route: {
|
||||||
params: { id: 100 },
|
params: { id: 100 },
|
||||||
name: 'external-user-profile'
|
name: 'external-user-profile'
|
||||||
},
|
},
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -192,14 +191,15 @@ describe('UserProfile', () => {
|
||||||
|
|
||||||
it('renders local profile', () => {
|
it('renders local profile', () => {
|
||||||
const wrapper = mount(UserProfile, {
|
const wrapper = mount(UserProfile, {
|
||||||
localVue,
|
global: {
|
||||||
store: localProfileStore,
|
plugins: [ localProfileStore ],
|
||||||
mocks: {
|
mocks: {
|
||||||
$route: {
|
$route: {
|
||||||
params: { name: 'testUser' },
|
params: { name: 'testUser' },
|
||||||
name: 'user-profile'
|
name: 'user-profile'
|
||||||
},
|
},
|
||||||
$t: (msg) => msg
|
$t: (msg) => msg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue