Merge pull request '2022.09 stable' (#160) from develop into stable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: AkkomaGang/pleroma-fe#160
This commit is contained in:
commit
d7499a1f91
88 changed files with 2411 additions and 240 deletions
|
@ -10,3 +10,5 @@ Contributors of this project.
|
|||
- shpuld (shpuld@shitposter.club): CSS and styling
|
||||
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
|
||||
- hj (hj@shigusegubu.club): Code
|
||||
- Sean King (seanking@freespeechextremist.com): Code
|
||||
- Tusooa Zhu (tusooa@kazv.moe): Code
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Pleroma-FE
|
||||
|
||||
![English OK](https://img.shields.io/badge/English-OK-blueviolet) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet)
|
||||
|
||||
This is a fork of Pleroma-FE from the Pleroma project, with support for new Akkoma features such as:
|
||||
- MFM support via [marked-mfm](https://akkoma.dev/sfr/marked-mfm)
|
||||
- Custom emoji reactions
|
||||
|
|
4
config/ihba.json
Normal file
4
config/ihba.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"target": "https://ihatebeinga.live",
|
||||
"staticConfigPreference": false
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
<body class="hidden">
|
||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||
<div id="app"></div>
|
||||
<div id="modal"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"@babel/runtime": "7.17.8",
|
||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "3.0.1",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||
|
|
|
@ -10,7 +10,9 @@ import MobilePostStatusButton from './components/mobile_post_status_button/mobil
|
|||
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
||||
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
|
||||
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
||||
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
|
||||
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
||||
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
|
||||
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
||||
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
@ -33,6 +35,8 @@ export default {
|
|||
SettingsModal,
|
||||
UserReportingModal,
|
||||
PostStatusModal,
|
||||
EditStatusModal,
|
||||
StatusHistoryModal,
|
||||
GlobalNoticeList
|
||||
},
|
||||
data: () => ({
|
||||
|
@ -83,6 +87,7 @@ export default {
|
|||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
|
||||
},
|
||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||
layoutType () { return this.$store.state.interface.layoutType },
|
||||
privateMode () { return this.$store.state.instance.private },
|
||||
reverseLayout () {
|
||||
|
|
|
@ -58,8 +58,10 @@
|
|||
<MobilePostStatusButton />
|
||||
<UserReportingModal />
|
||||
<PostStatusModal />
|
||||
<EditStatusModal v-if="editingAvailable" />
|
||||
<StatusHistoryModal v-if="editingAvailable" />
|
||||
<SettingsModal />
|
||||
<div id="modal" />
|
||||
<UpdateNotification />
|
||||
<GlobalNoticeList />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Cookies from 'js-cookie'
|
||||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import vClickOutside from 'click-outside-vue3'
|
||||
|
@ -48,6 +49,20 @@ const preloadFetch = async (request) => {
|
|||
}
|
||||
}
|
||||
|
||||
const resolveLanguage = (instanceLanguages) => {
|
||||
// First language in navigator.languages that is listed as an instance language
|
||||
// falls back to first instance language
|
||||
const navigatorLanguages = navigator.languages.map((x) => x.split('-')[0])
|
||||
|
||||
for (const navLanguage of navigatorLanguages) {
|
||||
if (instanceLanguages.includes(navLanguage)) {
|
||||
return navLanguage
|
||||
}
|
||||
}
|
||||
|
||||
return instanceLanguages[0]
|
||||
}
|
||||
|
||||
const getInstanceConfig = async ({ store }) => {
|
||||
try {
|
||||
const res = await preloadFetch('/api/v1/instance')
|
||||
|
@ -58,6 +73,10 @@ const getInstanceConfig = async ({ store }) => {
|
|||
|
||||
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
||||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
||||
// don't override cookie if set
|
||||
if (!Cookies.get('userLanguage')) {
|
||||
store.dispatch('setOption', { name: 'interfaceLanguage', value: resolveLanguage(data.languages) })
|
||||
}
|
||||
|
||||
if (vapidPublicKey) {
|
||||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
||||
|
@ -124,6 +143,11 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
|||
copyInstanceOption('hideBotIndication')
|
||||
copyInstanceOption('hideUserStats')
|
||||
copyInstanceOption('hideFilteredStatuses')
|
||||
copyInstanceOption('hideSiteName')
|
||||
copyInstanceOption('hideSiteFavicon')
|
||||
copyInstanceOption('showWiderShortcuts')
|
||||
copyInstanceOption('showNavShortcuts')
|
||||
copyInstanceOption('showPanelNavShortcuts')
|
||||
copyInstanceOption('logo')
|
||||
|
||||
store.dispatch('setInstanceOption', {
|
||||
|
@ -154,6 +178,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
|||
copyInstanceOption('alwaysShowSubjectInput')
|
||||
copyInstanceOption('showFeaturesPanel')
|
||||
copyInstanceOption('hideSitename')
|
||||
copyInstanceOption('renderMisskeyMarkdown')
|
||||
copyInstanceOption('sidebarRight')
|
||||
|
||||
return store.dispatch('setTheme', config['theme'])
|
||||
|
@ -248,8 +273,10 @@ const getNodeInfo = async ({ store }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
|
||||
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
|
||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
||||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
||||
store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
|
||||
|
||||
const uploadLimits = metadata.uploadLimits
|
||||
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
||||
|
@ -371,6 +398,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
store.dispatch('startFetchingAnnouncements')
|
||||
getTOS({ store })
|
||||
getStickers({ store })
|
||||
store.dispatch('getSupportedTranslationlanguages')
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import ProgressButton from '../progress_button/progress_button.vue'
|
||||
import Popover from '../popover/popover.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { mapState } from 'vuex'
|
||||
import {
|
||||
faEllipsisV
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
@ -14,13 +16,22 @@ const AccountActions = {
|
|||
'user', 'relationship'
|
||||
],
|
||||
data () {
|
||||
return { }
|
||||
return {
|
||||
showingConfirmBlock: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ProgressButton,
|
||||
Popover
|
||||
Popover,
|
||||
ConfirmModal
|
||||
},
|
||||
methods: {
|
||||
showConfirmBlock () {
|
||||
this.showingConfirmBlock = true
|
||||
},
|
||||
hideConfirmBlock () {
|
||||
this.showingConfirmBlock = false
|
||||
},
|
||||
showRepeats () {
|
||||
this.$store.dispatch('showReblogs', this.user.id)
|
||||
},
|
||||
|
@ -28,7 +39,15 @@ const AccountActions = {
|
|||
this.$store.dispatch('hideReblogs', this.user.id)
|
||||
},
|
||||
blockUser () {
|
||||
if (!this.shouldConfirmBlock) {
|
||||
this.doBlockUser()
|
||||
} else {
|
||||
this.showConfirmBlock()
|
||||
}
|
||||
},
|
||||
doBlockUser () {
|
||||
this.$store.dispatch('blockUser', this.user.id)
|
||||
this.hideConfirmBlock()
|
||||
},
|
||||
unblockUser () {
|
||||
this.$store.dispatch('unblockUser', this.user.id)
|
||||
|
@ -36,6 +55,14 @@ const AccountActions = {
|
|||
reportUser () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shouldConfirmBlock () {
|
||||
return this.$store.getters.mergedConfig.modalOnBlock
|
||||
},
|
||||
...mapState({
|
||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,27 @@
|
|||
</button>
|
||||
</template>
|
||||
</Popover>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmBlock"
|
||||
:title="$t('user_card.block_confirm_title')"
|
||||
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
||||
@accepted="doBlockUser"
|
||||
@cancelled="hideConfirmBlock"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="user_card.block_confirm"
|
||||
tag="span"
|
||||
>
|
||||
<template v-slot:user>
|
||||
<span
|
||||
v-text="user.screen_name_ui"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="panel panel-default announcements-page">
|
||||
<div class="panel-heading">
|
||||
<span>
|
||||
<div class="title">
|
||||
{{ $t('announcements.page_header') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<section
|
||||
|
|
|
@ -132,6 +132,9 @@ const Attachment = {
|
|||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
watch: {
|
||||
'attachment.description' (newVal) {
|
||||
this.localDescription = newVal
|
||||
},
|
||||
localDescription (newVal) {
|
||||
this.onEdit(newVal)
|
||||
}
|
||||
|
|
37
src/components/confirm_modal/confirm_modal.js
Normal file
37
src/components/confirm_modal/confirm_modal.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import DialogModal from '../dialog_modal/dialog_modal.vue'
|
||||
|
||||
/**
|
||||
* This component emits the following events:
|
||||
* cancelled, emitted when the action should not be performed;
|
||||
* accepted, emitted when the action should be performed;
|
||||
*
|
||||
* The caller should close this dialog after receiving any of the two events.
|
||||
*/
|
||||
const ConfirmModal = {
|
||||
components: {
|
||||
DialogModal
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
cancelText: {
|
||||
type: String
|
||||
},
|
||||
confirmText: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
onCancel () {
|
||||
this.$emit('cancelled')
|
||||
},
|
||||
onAccept () {
|
||||
this.$emit('accepted')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfirmModal
|
39
src/components/confirm_modal/confirm_modal.vue
Normal file
39
src/components/confirm_modal/confirm_modal.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<dialog-modal
|
||||
v-body-scroll-lock="true"
|
||||
class="confirm-modal"
|
||||
:on-cancel="onCancel"
|
||||
>
|
||||
<template #header>
|
||||
<span v-text="title" />
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
|
||||
<template #footer>
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click.prevent="onCancel"
|
||||
v-text="cancelText"
|
||||
/>
|
||||
<button
|
||||
class="btn button-default button-positive"
|
||||
@click.prevent="onAccept"
|
||||
v-text="confirmText"
|
||||
/>
|
||||
</template>
|
||||
</dialog-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../_variables';
|
||||
|
||||
.confirm-modal {
|
||||
.button-positive {
|
||||
border: 3px solid var(--accent, $fallback--link);
|
||||
border-radius: var(--btnRadius, $fallback--btnRadius);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="./confirm_modal.js"></script>
|
|
@ -1,6 +1,8 @@
|
|||
import { reduce, filter, findIndex, clone, get } from 'lodash'
|
||||
import Status from '../status/status.vue'
|
||||
import ThreadTree from '../thread_tree/thread_tree.vue'
|
||||
import { WSConnectionStatus } from '../../services/api/api.service.js'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
@ -77,6 +79,9 @@ const conversation = {
|
|||
const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
|
||||
return maxDepth >= 1 ? maxDepth : 1
|
||||
},
|
||||
streamingEnabled () {
|
||||
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
|
||||
},
|
||||
displayStyle () {
|
||||
return this.$store.getters.mergedConfig.conversationDisplay
|
||||
},
|
||||
|
@ -339,7 +344,11 @@ const conversation = {
|
|||
},
|
||||
maybeHighlight () {
|
||||
return this.isExpanded ? this.highlight : null
|
||||
}
|
||||
},
|
||||
...mapGetters(['mergedConfig']),
|
||||
...mapState({
|
||||
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
|
||||
})
|
||||
},
|
||||
components: {
|
||||
Status,
|
||||
|
@ -395,6 +404,11 @@ const conversation = {
|
|||
setHighlight (id) {
|
||||
if (!id) return
|
||||
this.highlight = id
|
||||
|
||||
if (!this.streamingEnabled) {
|
||||
this.$store.dispatch('fetchStatus', id)
|
||||
}
|
||||
|
||||
this.$store.dispatch('fetchFavsAndRepeats', id)
|
||||
this.$store.dispatch('fetchEmojiReactionsBy', id)
|
||||
},
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import SearchBar from 'components/search_bar/search_bar.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faSignInAlt,
|
||||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faCog,
|
||||
faGlobe,
|
||||
faBolt,
|
||||
faUsers,
|
||||
faCommentMedical,
|
||||
faBookmark,
|
||||
faInfoCircle
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
@ -19,18 +24,23 @@ library.add(
|
|||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faCog,
|
||||
faGlobe,
|
||||
faBolt,
|
||||
faUsers,
|
||||
faCommentMedical,
|
||||
faBookmark,
|
||||
faInfoCircle
|
||||
)
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SearchBar
|
||||
SearchBar,
|
||||
ConfirmModal
|
||||
},
|
||||
data: () => ({
|
||||
searchBarHidden: true,
|
||||
|
@ -40,7 +50,8 @@ export default {
|
|||
window.CSS.supports('-moz-mask-size', 'contain') ||
|
||||
window.CSS.supports('-ms-mask-size', 'contain') ||
|
||||
window.CSS.supports('-o-mask-size', 'contain')
|
||||
)
|
||||
),
|
||||
showingConfirmLogout: false
|
||||
}),
|
||||
computed: {
|
||||
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
|
||||
|
@ -65,20 +76,34 @@ export default {
|
|||
})
|
||||
},
|
||||
logo () { return this.$store.state.instance.logo },
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
},
|
||||
sitename () { return this.$store.state.instance.name },
|
||||
showNavShortcuts () {
|
||||
return this.mergedConfig.showNavShortcuts
|
||||
},
|
||||
showWiderShortcuts () {
|
||||
return this.mergedConfig.showWiderShortcuts
|
||||
},
|
||||
hideSiteFavicon () {
|
||||
return this.mergedConfig.hideSiteFavicon
|
||||
},
|
||||
hideSiteName () {
|
||||
return this.mergedConfig.hideSiteName
|
||||
},
|
||||
hideSitename () { return this.$store.state.instance.hideSitename },
|
||||
logoLeft () { return this.$store.state.instance.logoLeft },
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
privateMode () { return this.$store.state.instance.private }
|
||||
privateMode () { return this.$store.state.instance.private },
|
||||
shouldConfirmLogout () {
|
||||
return this.$store.getters.mergedConfig.modalOnLogout
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrollToTop () {
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
logout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('logout')
|
||||
},
|
||||
onSearchBarToggled (hidden) {
|
||||
this.searchBarHidden = hidden
|
||||
},
|
||||
|
|
|
@ -15,16 +15,16 @@
|
|||
display: grid;
|
||||
grid-template-rows: var(--navbar-height);
|
||||
grid-template-columns: 2fr auto 2fr;
|
||||
grid-template-areas: "sitename logo actions";
|
||||
grid-template-areas: "nav-left logo actions";
|
||||
box-sizing: border-box;
|
||||
padding: 0 1.2em;
|
||||
margin: auto;
|
||||
max-width: 980px;
|
||||
max-width: 1110px;
|
||||
}
|
||||
|
||||
&.-logoLeft .inner-nav {
|
||||
grid-template-columns: auto 2fr 2fr;
|
||||
grid-template-areas: "logo sitename actions";
|
||||
grid-template-areas: "logo nav-left actions";
|
||||
}
|
||||
|
||||
.button-default {
|
||||
|
@ -84,14 +84,21 @@
|
|||
}
|
||||
|
||||
.nav-icon {
|
||||
margin-left: 1em;
|
||||
margin-left: 0.2em;
|
||||
width: 2em;
|
||||
height: 100%;
|
||||
font-size: 130%;
|
||||
text-align: center;
|
||||
|
||||
&-logout {
|
||||
margin-left: 2em;
|
||||
&.router-link-active {
|
||||
font-size: 1.2em;
|
||||
margin-top: 0.05em;
|
||||
|
||||
.svg-inline--fa {
|
||||
font-weight: bolder;
|
||||
color: $fallback--text;
|
||||
color: var(--selectedMenuText, $fallback--text);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
}
|
||||
}
|
||||
|
||||
.svg-inline--fa {
|
||||
|
@ -100,8 +107,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
.sitename {
|
||||
grid-area: sitename;
|
||||
.-wide {
|
||||
.nav-icon {
|
||||
margin-left: 0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
padding-left: 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav-left-wrapper {
|
||||
grid-area: nav-left;
|
||||
|
||||
.favicon {
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
|
|
@ -5,16 +5,79 @@
|
|||
:class="{ '-logoLeft': logoLeft }"
|
||||
@click="scrollToTop()"
|
||||
>
|
||||
<div class="inner-nav">
|
||||
<div class="item sitename">
|
||||
<div
|
||||
class="inner-nav"
|
||||
:class="{ '-wide': showWiderShortcuts }"
|
||||
>
|
||||
<div class="item nav-left-wrapper">
|
||||
<router-link
|
||||
v-if="!hideSitename"
|
||||
class="site-name"
|
||||
class="site-brand"
|
||||
:to="{ name: 'root' }"
|
||||
active-class="home"
|
||||
>
|
||||
{{ sitename }}
|
||||
<img
|
||||
v-if="!hideSiteFavicon"
|
||||
class="favicon"
|
||||
src="/favicon.png"
|
||||
>
|
||||
<span
|
||||
v-if="!hideSiteName"
|
||||
class="site-name"
|
||||
>
|
||||
{{ sitename }}
|
||||
</span>
|
||||
</router-link>
|
||||
<div
|
||||
v-if="(currentUser || !privateMode) && showNavShortcuts"
|
||||
class="nav-items left"
|
||||
>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'friends' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="home"
|
||||
:title="$t('nav.home_timeline')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'public-timeline' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="users"
|
||||
:title="$t('nav.public_tl')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'bubble-timeline' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="circle"
|
||||
:title="$t('nav.bubble_timeline')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'public-external-timeline' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="globe"
|
||||
:title="$t('nav.twkn')"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<router-link
|
||||
class="logo"
|
||||
|
@ -36,6 +99,47 @@
|
|||
@toggled="onSearchBarToggled"
|
||||
@click.stop
|
||||
/>
|
||||
<div
|
||||
v-if="(currentUser || !privateMode) && showNavShortcuts"
|
||||
class="nav-items right"
|
||||
>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
class="nav-icon"
|
||||
:to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="bolt"
|
||||
:title="$t('nav.interactions')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'lists' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="list"
|
||||
:title="$t('nav.lists')"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="currentUser"
|
||||
:to="{ name: 'bookmarks' }"
|
||||
class="nav-icon"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="bookmark"
|
||||
:title="$t('nav.bookmarks')"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
<button
|
||||
class="button-unstyled nav-icon"
|
||||
@click.stop="openSettingsModal"
|
||||
|
@ -61,20 +165,20 @@
|
|||
:title="$t('nav.administration')"
|
||||
/>
|
||||
</a>
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled nav-icon nav-icon-logout"
|
||||
@click.prevent="logout"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="sign-out-alt"
|
||||
:title="$t('login.logout')"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmLogout"
|
||||
:title="$t('login.logout_confirm_title')"
|
||||
:confirm-text="$t('login.logout_confirm_accept_button')"
|
||||
:cancel-text="$t('login.logout_confirm_cancel_button')"
|
||||
@accepted="doLogout"
|
||||
@cancelled="hideConfirmLogout"
|
||||
>
|
||||
{{ $t('login.logout_confirm') }}
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</nav>
|
||||
</template>
|
||||
<script src="./desktop_nav.js"></script>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
right: 0;
|
||||
top: 0;
|
||||
background: rgba(27,31,35,.5);
|
||||
z-index: 99;
|
||||
z-index: 2000;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,9 +51,10 @@
|
|||
margin: 15vh auto;
|
||||
position: fixed;
|
||||
transform: translateX(-50%);
|
||||
z-index: 999;
|
||||
z-index: 2001;
|
||||
cursor: default;
|
||||
display: block;
|
||||
width: max-content;
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
|
||||
|
|
75
src/components/edit_status_modal/edit_status_modal.js
Normal file
75
src/components/edit_status_modal/edit_status_modal.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import Modal from '../modal/modal.vue'
|
||||
import statusPosterService from '../../services/status_poster/status_poster.service.js'
|
||||
import get from 'lodash/get'
|
||||
|
||||
const EditStatusModal = {
|
||||
components: {
|
||||
PostStatusForm,
|
||||
Modal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
resettingForm: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
},
|
||||
modalActivated () {
|
||||
return this.$store.state.editStatus.modalActivated
|
||||
},
|
||||
isFormVisible () {
|
||||
return this.isLoggedIn && !this.resettingForm && this.modalActivated
|
||||
},
|
||||
params () {
|
||||
return this.$store.state.editStatus.params || {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
params (newVal, oldVal) {
|
||||
if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) {
|
||||
this.resettingForm = true
|
||||
this.$nextTick(() => {
|
||||
this.resettingForm = false
|
||||
})
|
||||
}
|
||||
},
|
||||
isFormVisible (val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus())
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) {
|
||||
const params = {
|
||||
store: this.$store,
|
||||
statusId: this.$store.state.editStatus.params.statusId,
|
||||
status,
|
||||
spoilerText,
|
||||
sensitive,
|
||||
poll,
|
||||
media,
|
||||
contentType
|
||||
}
|
||||
|
||||
return statusPosterService.editStatus(params)
|
||||
.then((data) => {
|
||||
return data
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error editing status', err)
|
||||
return {
|
||||
error: err.message
|
||||
}
|
||||
})
|
||||
},
|
||||
closeModal () {
|
||||
this.$store.dispatch('closeEditStatusModal')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EditStatusModal
|
48
src/components/edit_status_modal/edit_status_modal.vue
Normal file
48
src/components/edit_status_modal/edit_status_modal.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<Modal
|
||||
v-if="isFormVisible"
|
||||
class="edit-form-modal-view"
|
||||
@backdropClicked="closeModal"
|
||||
>
|
||||
<div class="edit-form-modal-panel panel">
|
||||
<div class="panel-heading">
|
||||
{{ $t('post_status.edit_status') }}
|
||||
</div>
|
||||
<PostStatusForm
|
||||
class="panel-body"
|
||||
v-bind="params"
|
||||
@posted="closeModal"
|
||||
:disablePolls="true"
|
||||
:disableVisibilitySelector="true"
|
||||
:post-handler="doEditStatus"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script src="./edit_status_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-view.edit-form-modal-view {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.edit-form-modal-panel {
|
||||
flex-shrink: 0;
|
||||
margin-top: 25%;
|
||||
margin-bottom: 2em;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
|
||||
@media (orientation: landscape) {
|
||||
margin-top: 8%;
|
||||
}
|
||||
|
||||
.form-bottom-left {
|
||||
max-width: 6.5em;
|
||||
|
||||
.emoji-icon {
|
||||
justify-content: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -46,14 +46,6 @@ const EmojiReactions = {
|
|||
reactedWith (emoji) {
|
||||
return this.status.emoji_reactions.find(r => r.name === emoji).me
|
||||
},
|
||||
isLocalReaction (emojiUrl) {
|
||||
if (!emojiUrl) return true
|
||||
const reacted = this.accountsForEmoji[emojiUrl]
|
||||
if (reacted.length === 0) {
|
||||
return true
|
||||
}
|
||||
return reacted[0].is_local
|
||||
},
|
||||
fetchEmojiReactionsByIfMissing () {
|
||||
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
|
||||
if (hasNoAccounts) {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<button
|
||||
class="emoji-reaction btn button-default"
|
||||
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
|
||||
:disabled="!isLocalReaction(reaction.url)"
|
||||
@click="emojiOnClick(reaction.name, $event)"
|
||||
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
||||
>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Popover from '../popover/popover.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEllipsisH,
|
||||
|
@ -6,7 +7,8 @@ import {
|
|||
faEyeSlash,
|
||||
faThumbtack,
|
||||
faShareAlt,
|
||||
faExternalLinkAlt
|
||||
faExternalLinkAlt,
|
||||
faHistory
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faBookmark as faBookmarkReg,
|
||||
|
@ -21,19 +23,49 @@ library.add(
|
|||
faThumbtack,
|
||||
faShareAlt,
|
||||
faExternalLinkAlt,
|
||||
faFlag
|
||||
faFlag,
|
||||
faHistory
|
||||
)
|
||||
|
||||
const ExtraButtons = {
|
||||
props: [ 'status' ],
|
||||
components: { Popover },
|
||||
props: ['status'],
|
||||
components: {
|
||||
Popover,
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
expanded: false,
|
||||
showingDeleteDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteStatus () {
|
||||
const confirmed = window.confirm(this.$t('status.delete_confirm'))
|
||||
if (confirmed) {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
if (this.shouldConfirmDelete) {
|
||||
this.showDeleteStatusConfirmDialog()
|
||||
} else {
|
||||
this.doDeleteStatus()
|
||||
}
|
||||
},
|
||||
doDeleteStatus () {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
this.hideDeleteStatusConfirmDialog()
|
||||
},
|
||||
showDeleteStatusConfirmDialog () {
|
||||
this.showingDeleteDialog = true
|
||||
},
|
||||
hideDeleteStatusConfirmDialog () {
|
||||
this.showingDeleteDialog = false
|
||||
},
|
||||
translateStatus () {
|
||||
if (this.noTranslationTargetSet) {
|
||||
this.$store.dispatch('pushGlobalNotice', { messageKey: 'toast.no_translation_target_set', level: 'info' })
|
||||
}
|
||||
const translateTo = this.$store.getters.mergedConfig.translationLanguage || this.$store.state.instance.interfaceLanguage
|
||||
this.$store.dispatch('translateStatus', { id: this.status.id, language: translateTo })
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
},
|
||||
pinStatus () {
|
||||
this.$store.dispatch('pinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
|
@ -71,6 +103,25 @@ const ExtraButtons = {
|
|||
},
|
||||
reportStatus () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] })
|
||||
},
|
||||
editStatus () {
|
||||
this.$store.dispatch('fetchStatusSource', { id: this.status.id })
|
||||
.then(data => this.$store.dispatch('openEditStatusModal', {
|
||||
statusId: this.status.id,
|
||||
subject: data.spoiler_text,
|
||||
statusText: data.text,
|
||||
statusIsSensitive: this.status.nsfw,
|
||||
statusPoll: this.status.poll,
|
||||
statusFiles: [...this.status.attachments],
|
||||
visibility: this.status.visibility,
|
||||
statusContentType: data.content_type
|
||||
}))
|
||||
},
|
||||
showStatusHistory () {
|
||||
const originalStatus = { ...this.status }
|
||||
const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html']
|
||||
stripFieldsList.forEach(p => delete originalStatus[p])
|
||||
this.$store.dispatch('openStatusHistoryModal', originalStatus)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -89,9 +140,26 @@ const ExtraButtons = {
|
|||
canMute () {
|
||||
return !!this.currentUser
|
||||
},
|
||||
canTranslate () {
|
||||
return this.$store.state.instance.translationEnabled === true
|
||||
},
|
||||
noTranslationTargetSet () {
|
||||
return this.$store.getters.mergedConfig.translationLanguage === undefined
|
||||
},
|
||||
statusLink () {
|
||||
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
|
||||
}
|
||||
if (this.status.is_local) {
|
||||
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
|
||||
} else {
|
||||
return this.status.external_url
|
||||
}
|
||||
},
|
||||
shouldConfirmDelete () {
|
||||
return this.$store.getters.mergedConfig.modalOnDelete
|
||||
},
|
||||
isEdited () {
|
||||
return this.status.edited_at !== null
|
||||
},
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,28 @@
|
|||
icon="bookmark"
|
||||
/><span>{{ $t("status.unbookmark") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="ownStatus && editingAvailable"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="editStatus"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="pen"
|
||||
/><span>{{ $t("status.edit") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="isEdited && editingAvailable"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="showStatusHistory"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="history"
|
||||
/><span>{{ $t("status.edit_history") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canDelete"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
|
@ -116,6 +138,27 @@
|
|||
:icon="['far', 'flag']"
|
||||
/><span>{{ $t("user_card.report") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canTranslate"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="translateStatus"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="globe"
|
||||
/><span>{{ $t("status.translate") }}</span>
|
||||
|
||||
<template v-if="noTranslationTargetSet">
|
||||
<span class="dropdown-item-icon__badge warning">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="exclamation-triangle"
|
||||
name="test"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:trigger>
|
||||
|
@ -125,6 +168,18 @@
|
|||
icon="ellipsis-h"
|
||||
/>
|
||||
</button>
|
||||
<teleport to="#modal">
|
||||
<ConfirmModal
|
||||
v-if="showingDeleteDialog"
|
||||
:title="$t('status.delete_confirm_title')"
|
||||
:cancel-text="$t('status.delete_confirm_cancel_button')"
|
||||
:confirm-text="$t('status.delete_confirm_accept_button')"
|
||||
@cancelled="hideDeleteStatusConfirmDialog"
|
||||
@accepted="doDeleteStatus"
|
||||
>
|
||||
{{ $t('status.delete_confirm') }}
|
||||
</ConfirmModal>
|
||||
</teleport>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
||||
export default {
|
||||
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inProgress: false
|
||||
inProgress: false,
|
||||
showingConfirmUnfollow: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shouldConfirmUnfollow () {
|
||||
return this.$store.getters.mergedConfig.modalOnUnfollow
|
||||
},
|
||||
isPressed () {
|
||||
return this.inProgress || this.relationship.following
|
||||
},
|
||||
|
@ -35,6 +43,12 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
showConfirmUnfollow () {
|
||||
this.showingConfirmUnfollow = true
|
||||
},
|
||||
hideConfirmUnfollow () {
|
||||
this.showingConfirmUnfollow = false
|
||||
},
|
||||
onClick () {
|
||||
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
|
||||
},
|
||||
|
@ -45,12 +59,21 @@ export default {
|
|||
})
|
||||
},
|
||||
unfollow () {
|
||||
if (this.shouldConfirmUnfollow) {
|
||||
this.showConfirmUnfollow()
|
||||
} else {
|
||||
this.doUnfollow()
|
||||
}
|
||||
},
|
||||
doUnfollow () {
|
||||
const store = this.$store
|
||||
this.inProgress = true
|
||||
requestUnfollow(this.relationship.id, store).then(() => {
|
||||
this.inProgress = false
|
||||
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
|
||||
})
|
||||
|
||||
this.hideConfirmUnfollow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,27 @@
|
|||
@click="onClick"
|
||||
>
|
||||
{{ label }}
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmUnfollow"
|
||||
:title="$t('user_card.unfollow_confirm_title')"
|
||||
:confirm-text="$t('user_card.unfollow_confirm_accept_button')"
|
||||
:cancel-text="$t('user_card.unfollow_confirm_cancel_button')"
|
||||
@accepted="doUnfollow"
|
||||
@cancelled="hideConfirmUnfollow"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="user_card.unfollow_confirm"
|
||||
tag="span"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
v-text="user.screen_name_ui"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</confirm-modal>
|
||||
</teleport>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
|
||||
|
||||
const FollowRequestCard = {
|
||||
props: ['user'],
|
||||
components: {
|
||||
BasicUserCard
|
||||
BasicUserCard,
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showingApproveConfirmDialog: false,
|
||||
showingDenyConfirmDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
findFollowRequestNotificationId () {
|
||||
|
@ -13,7 +21,26 @@ const FollowRequestCard = {
|
|||
)
|
||||
return notif && notif.id
|
||||
},
|
||||
showApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = true
|
||||
},
|
||||
hideApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = false
|
||||
},
|
||||
showDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = true
|
||||
},
|
||||
hideDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = false
|
||||
},
|
||||
approveUser () {
|
||||
if (this.shouldConfirmApprove) {
|
||||
this.showApproveConfirmDialog()
|
||||
} else {
|
||||
this.doApprove()
|
||||
}
|
||||
},
|
||||
doApprove () {
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
|
||||
|
@ -25,14 +52,34 @@ const FollowRequestCard = {
|
|||
notification.type = 'follow'
|
||||
}
|
||||
})
|
||||
this.hideApproveConfirmDialog()
|
||||
},
|
||||
denyUser () {
|
||||
if (this.shouldConfirmDeny) {
|
||||
this.showDenyConfirmDialog()
|
||||
} else {
|
||||
this.doDeny()
|
||||
}
|
||||
},
|
||||
doDeny () {
|
||||
const notifId = this.findFollowRequestNotificationId()
|
||||
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
})
|
||||
this.hideDenyConfirmDialog()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mergedConfig () {
|
||||