Merge branch 'timed-user-mutes' into 'develop'
Timed user mutes See merge request pleroma/pleroma-fe!2197
This commit is contained in:
commit
e747ee896e
30 changed files with 513 additions and 188 deletions
1
changelog.d/timed.add
Normal file
1
changelog.d/timed.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Support for expiring mutes and blocks (if available)
|
||||||
|
|
@ -679,11 +679,6 @@ option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-block {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-group {
|
.btn-group {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,7 @@ const getNodeInfo = async ({ store }) => {
|
||||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
||||||
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
|
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
|
||||||
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
|
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'blockExpiration', value: features.includes('pleroma:block_expiration') })
|
||||||
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
|
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
|
||||||
|
|
||||||
const uploadLimits = metadata.uploadLimits
|
const uploadLimits = metadata.uploadLimits
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import ProgressButton from '../progress_button/progress_button.vue'
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
|
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
|
||||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
|
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faEllipsisV
|
faEllipsisV
|
||||||
|
|
@ -27,15 +28,10 @@ const AccountActions = {
|
||||||
ProgressButton,
|
ProgressButton,
|
||||||
Popover,
|
Popover,
|
||||||
UserListMenu,
|
UserListMenu,
|
||||||
ConfirmModal
|
ConfirmModal,
|
||||||
|
UserTimedFilterModal
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showConfirmBlock () {
|
|
||||||
this.showingConfirmBlock = true
|
|
||||||
},
|
|
||||||
hideConfirmBlock () {
|
|
||||||
this.showingConfirmBlock = false
|
|
||||||
},
|
|
||||||
showConfirmRemoveUserFromFollowers () {
|
showConfirmRemoveUserFromFollowers () {
|
||||||
this.showingConfirmRemoveFollower = true
|
this.showingConfirmRemoveFollower = true
|
||||||
},
|
},
|
||||||
|
|
@ -49,10 +45,14 @@ const AccountActions = {
|
||||||
this.$store.dispatch('hideReblogs', this.user.id)
|
this.$store.dispatch('hideReblogs', this.user.id)
|
||||||
},
|
},
|
||||||
blockUser () {
|
blockUser () {
|
||||||
|
if (this.$refs.timedBlockDialog) {
|
||||||
|
this.$refs.timedBlockDialog.optionallyPrompt()
|
||||||
|
} else {
|
||||||
if (!this.shouldConfirmBlock) {
|
if (!this.shouldConfirmBlock) {
|
||||||
this.doBlockUser()
|
this.doBlockUser()
|
||||||
} else {
|
} else {
|
||||||
this.showConfirmBlock()
|
this.showingConfirmBlock = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doBlockUser () {
|
doBlockUser () {
|
||||||
|
|
@ -91,6 +91,7 @@ const AccountActions = {
|
||||||
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
||||||
},
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
|
blockExpirationSupported: state => state.instance.blockExpiration,
|
||||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@
|
||||||
</Popover>
|
</Popover>
|
||||||
<teleport to="#modal">
|
<teleport to="#modal">
|
||||||
<confirm-modal
|
<confirm-modal
|
||||||
v-if="showingConfirmBlock"
|
v-if="showingConfirmBlock && !blockExpirationSupported"
|
||||||
|
ref="blockDialog"
|
||||||
:title="$t('user_card.block_confirm_title')"
|
:title="$t('user_card.block_confirm_title')"
|
||||||
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
||||||
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
||||||
|
|
@ -137,6 +138,12 @@
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</confirm-modal>
|
</confirm-modal>
|
||||||
|
<UserTimedFilterModal
|
||||||
|
v-if="blockExpirationSupported"
|
||||||
|
:is-mute="false"
|
||||||
|
:user="user"
|
||||||
|
ref="timedBlockDialog"
|
||||||
|
/>
|
||||||
</teleport>
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
|
||||||
const BlockCard = {
|
const BlockCard = {
|
||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
progress: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
user () {
|
user () {
|
||||||
return this.$store.getters.findUser(this.userId)
|
return this.$store.getters.findUser(this.userId)
|
||||||
|
|
@ -16,23 +13,32 @@ const BlockCard = {
|
||||||
},
|
},
|
||||||
blocked () {
|
blocked () {
|
||||||
return this.relationship.blocking
|
return this.relationship.blocking
|
||||||
}
|
},
|
||||||
|
blockExpiryAvailable () {
|
||||||
|
return this.user.block_expires_at !== undefined
|
||||||
|
},
|
||||||
|
blockExpiry () {
|
||||||
|
return this.user.block_expires_at == null
|
||||||
|
? this.$t('user_card.block_expires_forever')
|
||||||
|
: this.$t('user_card.block_expires_at', [new Date(this.user.mute_expires_at).toLocaleString()])
|
||||||
|
},
|
||||||
|
...mapState({
|
||||||
|
blockExpirationSupported: state => state.instance.blockExpiration,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BasicUserCard
|
BasicUserCard
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
unblockUser () {
|
unblockUser () {
|
||||||
this.progress = true
|
this.$store.dispatch('unblockUser', this.user.id)
|
||||||
this.$store.dispatch('unblockUser', this.user.id).then(() => {
|
|
||||||
this.progress = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
blockUser () {
|
blockUser () {
|
||||||
this.progress = true
|
if (this.blockExpirationSupported) {
|
||||||
this.$store.dispatch('blockUser', this.user.id).then(() => {
|
this.$refs.timedBlockDialog.optionallyPrompt()
|
||||||
this.progress = false
|
} else {
|
||||||
})
|
this.$store.dispatch('blockUser', this.user.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<basic-user-card :user="user">
|
<basic-user-card :user="user">
|
||||||
<div class="block-card-content-container">
|
<div class="block-card-content-container">
|
||||||
|
<span v-if="blocked && blockExpiryAvailable" class="alert neutral">
|
||||||
|
{{ blockExpiry }}
|
||||||
|
</span>
|
||||||
|
{{ ' ' }}
|
||||||
<button
|
<button
|
||||||
v-if="blocked"
|
v-if="blocked"
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
:disabled="progress"
|
|
||||||
@click="unblockUser"
|
@click="unblockUser"
|
||||||
>
|
>
|
||||||
<template v-if="progress">
|
|
||||||
{{ $t('user_card.unblock_progress') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t('user_card.unblock') }}
|
{{ $t('user_card.unblock') }}
|
||||||
</template>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
:disabled="progress"
|
|
||||||
@click="blockUser"
|
@click="blockUser"
|
||||||
>
|
>
|
||||||
<template v-if="progress">
|
|
||||||
{{ $t('user_card.block_progress') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t('user_card.block') }}
|
{{ $t('user_card.block') }}
|
||||||
</template>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<UserTimedFilterModal
|
||||||
|
:user="user"
|
||||||
|
:is-mute="false"
|
||||||
|
ref="timedBlockDialog"
|
||||||
|
/>
|
||||||
|
</teleport>
|
||||||
</basic-user-card>
|
</basic-user-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
<slot name="footerLeft" />
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
@click.prevent="onAccept"
|
@click.prevent="onAccept"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { unitToSeconds } from 'src/services/date_utils/date_utils.js'
|
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
import ConfirmModal from './confirm_modal.vue'
|
import ConfirmModal from './confirm_modal.vue'
|
||||||
|
|
@ -8,21 +7,13 @@ export default {
|
||||||
props: ['type', 'user', 'status'],
|
props: ['type', 'user', 'status'],
|
||||||
emits: ['hide', 'show', 'muted'],
|
emits: ['hide', 'show', 'muted'],
|
||||||
data: () => ({
|
data: () => ({
|
||||||
showing: false,
|
showing: false
|
||||||
muteExpiryAmount: 2,
|
|
||||||
muteExpiryUnit: 'hours'
|
|
||||||
}),
|
}),
|
||||||
components: {
|
components: {
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
Select
|
Select
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
muteExpiryValue () {
|
|
||||||
unitToSeconds(this.muteExpiryUnit, this.muteExpiryAmount)
|
|
||||||
},
|
|
||||||
muteExpiryUnits () {
|
|
||||||
return ['minutes', 'hours', 'days']
|
|
||||||
},
|
|
||||||
domain () {
|
domain () {
|
||||||
return this.user.fqn.split('@')[1]
|
return this.user.fqn.split('@')[1]
|
||||||
},
|
},
|
||||||
|
|
@ -31,13 +22,8 @@ export default {
|
||||||
return 'status.mute_domain_confirm'
|
return 'status.mute_domain_confirm'
|
||||||
} else if (this.type === 'conversation') {
|
} else if (this.type === 'conversation') {
|
||||||
return 'status.mute_conversation_confirm'
|
return 'status.mute_conversation_confirm'
|
||||||
} else {
|
|
||||||
return 'user_card.mute_confirm'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userIsMuted () {
|
|
||||||
return this.$store.getters.relationship(this.user.id).muting
|
|
||||||
},
|
|
||||||
conversationIsMuted () {
|
conversationIsMuted () {
|
||||||
return this.status.conversation_muted
|
return this.status.conversation_muted
|
||||||
},
|
},
|
||||||
|
|
@ -49,12 +35,9 @@ export default {
|
||||||
case 'domain': {
|
case 'domain': {
|
||||||
return this.mergedConfig.modalOnMuteDomain
|
return this.mergedConfig.modalOnMuteDomain
|
||||||
}
|
}
|
||||||
case 'conversation': {
|
default: { // conversation
|
||||||
return this.mergedConfig.modalOnMuteConversation
|
return this.mergedConfig.modalOnMuteConversation
|
||||||
}
|
}
|
||||||
default: {
|
|
||||||
return this.mergedConfig.modalOnMute
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...mapGetters(['mergedConfig'])
|
...mapGetters(['mergedConfig'])
|
||||||
|
|
@ -79,7 +62,7 @@ export default {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case 'domain': {
|
case 'domain': {
|
||||||
if (!this.domainIsMuted) {
|
if (!this.domainIsMuted) {
|
||||||
this.$store.dispatch('muteDomain', { id: this.domain, expiresIn: this.muteExpiryValue })
|
this.$store.dispatch('muteDomain', { id: this.domain })
|
||||||
} else {
|
} else {
|
||||||
this.$store.dispatch('unmuteDomain', { id: this.domain })
|
this.$store.dispatch('unmuteDomain', { id: this.domain })
|
||||||
}
|
}
|
||||||
|
|
@ -87,20 +70,12 @@ export default {
|
||||||
}
|
}
|
||||||
case 'conversation': {
|
case 'conversation': {
|
||||||
if (!this.conversationIsMuted) {
|
if (!this.conversationIsMuted) {
|
||||||
this.$store.dispatch('muteConversation', { id: this.status.id, expiresIn: this.muteExpiryValue })
|
this.$store.dispatch('muteConversation', { id: this.status.id })
|
||||||
} else {
|
} else {
|
||||||
this.$store.dispatch('unmuteConversation', { id: this.status.id })
|
this.$store.dispatch('unmuteConversation', { id: this.status.id })
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
|
||||||
if (!this.userIsMuted) {
|
|
||||||
this.$store.dispatch('muteUser', { id: this.user.id, expiresIn: this.muteExpiryValue })
|
|
||||||
} else {
|
|
||||||
this.$store.dispatch('unmuteUser', { id: this.user.id })
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.$emit('muted')
|
this.$emit('muted')
|
||||||
this.hide()
|
this.hide()
|
||||||
|
|
|
||||||
|
|
@ -18,36 +18,6 @@
|
||||||
<span v-text="user.screen_name_ui" />
|
<span v-text="user.screen_name_ui" />
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
<div
|
|
||||||
v-if="type !== 'domain'"
|
|
||||||
class="mute-expiry"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
{{ $t('user_card.mute_duration_prompt') }}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model="muteExpiryAmount"
|
|
||||||
type="number"
|
|
||||||
class="input expiry-amount hide-number-spinner"
|
|
||||||
:min="0"
|
|
||||||
>
|
|
||||||
{{ ' ' }}
|
|
||||||
<Select
|
|
||||||
v-model="muteExpiryUnit"
|
|
||||||
unstyled="true"
|
|
||||||
class="expiry-unit"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="unit in muteExpiryUnits"
|
|
||||||
:key="unit"
|
|
||||||
:value="unit"
|
|
||||||
>
|
|
||||||
{{ $t(`time.unit.${unit}_short`, ['']) }}
|
|
||||||
</option>
|
|
||||||
</Select>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</confirm-modal>
|
</confirm-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
|
||||||
|
|
||||||
const MuteCard = {
|
const MuteCard = {
|
||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
progress: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
user () {
|
user () {
|
||||||
return this.$store.getters.findUser(this.userId)
|
return this.$store.getters.findUser(this.userId)
|
||||||
|
|
@ -16,23 +12,26 @@ const MuteCard = {
|
||||||
},
|
},
|
||||||
muted () {
|
muted () {
|
||||||
return this.relationship.muting
|
return this.relationship.muting
|
||||||
|
},
|
||||||
|
muteExpiryAvailable () {
|
||||||
|
return this.user.mute_expires_at !== undefined
|
||||||
|
},
|
||||||
|
muteExpiry () {
|
||||||
|
return this.user.mute_expires_at == null
|
||||||
|
? this.$t('user_card.mute_expires_forever')
|
||||||
|
: this.$t('user_card.mute_expires_at', [new Date(this.user.mute_expires_at).toLocaleString()])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BasicUserCard
|
BasicUserCard,
|
||||||
|
UserTimedFilterModal
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
unmuteUser () {
|
unmuteUser () {
|
||||||
this.progress = true
|
this.$store.dispatch('unmuteUser', this.userId)
|
||||||
this.$store.dispatch('unmuteUser', this.userId).then(() => {
|
|
||||||
this.progress = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
muteUser () {
|
muteUser () {
|
||||||
this.progress = true
|
this.$refs.timedMuteDialog.optionallyPrompt()
|
||||||
this.$store.dispatch('muteUser', this.userId).then(() => {
|
|
||||||
this.progress = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<basic-user-card :user="user">
|
<basic-user-card :user="user">
|
||||||
<div class="mute-card-content-container">
|
<div class="mute-card-content-container">
|
||||||
|
<span v-if="muted && muteExpiryAvailable" class="alert neutral">
|
||||||
|
{{ muteExpiry }}
|
||||||
|
</span>
|
||||||
|
{{ ' ' }}
|
||||||
<button
|
<button
|
||||||
v-if="muted"
|
v-if="muted"
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
:disabled="progress"
|
|
||||||
@click="unmuteUser"
|
@click="unmuteUser"
|
||||||
>
|
>
|
||||||
<template v-if="progress">
|
|
||||||
{{ $t('user_card.unmute_progress') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t('user_card.unmute') }}
|
{{ $t('user_card.unmute') }}
|
||||||
</template>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
:disabled="progress"
|
|
||||||
@click="muteUser"
|
@click="muteUser"
|
||||||
>
|
>
|
||||||
<template v-if="progress">
|
|
||||||
{{ $t('user_card.mute_progress') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t('user_card.mute') }}
|
{{ $t('user_card.mute') }}
|
||||||
</template>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<UserTimedFilterModal
|
||||||
|
:user="user"
|
||||||
|
:is-mute="true"
|
||||||
|
ref="timedMuteDialog"
|
||||||
|
/>
|
||||||
|
</teleport>
|
||||||
</basic-user-card>
|
</basic-user-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import { mapState, mapActions } from 'pinia'
|
import { mapState, mapActions } from 'pinia'
|
||||||
|
import { mapState as mapVuexState } from 'vuex'
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
|
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
|
||||||
|
|
@ -30,6 +31,11 @@ const FilteringTab = {
|
||||||
value: mode,
|
value: mode,
|
||||||
label: this.$t(`settings.reply_visibility_${mode}`)
|
label: this.$t(`settings.reply_visibility_${mode}`)
|
||||||
})),
|
})),
|
||||||
|
muteBlockLv1Options: ['ask', 'forever', 'temporarily'].map(mode => ({
|
||||||
|
key: mode,
|
||||||
|
value: mode,
|
||||||
|
label: this.$t(`user_card.mute_block_${mode}`)
|
||||||
|
})),
|
||||||
muteFiltersDraftObject: cloneDeep(useServerSideStorageStore().prefsStorage.simple.muteFilters),
|
muteFiltersDraftObject: cloneDeep(useServerSideStorageStore().prefsStorage.simple.muteFilters),
|
||||||
muteFiltersDraftDirty: Object.fromEntries(
|
muteFiltersDraftDirty: Object.fromEntries(
|
||||||
Object.entries(
|
Object.entries(
|
||||||
|
|
@ -93,6 +99,43 @@ const FilteringTab = {
|
||||||
muteFiltersObject: store => store.prefsStorage.simple.muteFilters
|
muteFiltersObject: store => store.prefsStorage.simple.muteFilters
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
...mapVuexState({
|
||||||
|
blockExpirationSupported: state => state.instance.blockExpiration
|
||||||
|
}),
|
||||||
|
onMuteDefaultActionLv1: {
|
||||||
|
get () {
|
||||||
|
const value = this.$store.state.config.onMuteDefaultAction
|
||||||
|
if (value === 'ask' || value === 'forever') {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return 'temporarily'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set (value) {
|
||||||
|
let realValue = value
|
||||||
|
if (value !== 'ask' && value !== 'forever') {
|
||||||
|
realValue = '14d'
|
||||||
|
}
|
||||||
|
this.$store.dispatch('setOption', { name: 'onMuteDefaultAction', value: realValue })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBlockDefaultActionLv1: {
|
||||||
|
get () {
|
||||||
|
const value = this.$store.state.config.onBlockDefaultAction
|
||||||
|
if (value === 'ask' || value === 'forever') {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return 'temporarily'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set (value) {
|
||||||
|
let realValue = value
|
||||||
|
if (value !== 'ask' && value !== 'forever') {
|
||||||
|
realValue = '14d'
|
||||||
|
}
|
||||||
|
this.$store.dispatch('setOption', { name: 'onBlockDefaultAction', value: realValue })
|
||||||
|
}
|
||||||
|
},
|
||||||
muteFiltersDraft () {
|
muteFiltersDraft () {
|
||||||
return Object.entries(this.muteFiltersDraftObject)
|
return Object.entries(this.muteFiltersDraftObject)
|
||||||
},
|
},
|
||||||
|
|
@ -107,7 +150,7 @@ const FilteringTab = {
|
||||||
...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']),
|
...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']),
|
||||||
getDatetimeLocal (timestamp) {
|
getDatetimeLocal (timestamp) {
|
||||||
const date = new Date(timestamp)
|
const date = new Date(timestamp)
|
||||||
let fmt = new Intl.NumberFormat("en-US", {minimumIntegerDigits: 2})
|
const fmt = new Intl.NumberFormat("en-US", {minimumIntegerDigits: 2})
|
||||||
const datetime = [
|
const datetime = [
|
||||||
date.getFullYear(),
|
date.getFullYear(),
|
||||||
'-',
|
'-',
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,66 @@
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<h2>{{ $t('settings.filter.mute_filter') }}</h2>
|
<h2>{{ $t('settings.filter.mute_filter') }}</h2>
|
||||||
<ul class="setting-list">
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
{{ $t('user_card.default_mute_expiration') }}
|
||||||
|
<Select
|
||||||
|
id="onMuteDefaultActionLv1"
|
||||||
|
v-model="onMuteDefaultActionLv1"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in muteBlockLv1Options"
|
||||||
|
:key="option.key"
|
||||||
|
:value="option.value"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</Select>
|
||||||
|
<ul
|
||||||
|
class="setting-list suboptions"
|
||||||
|
v-if="onMuteDefaultActionLv1 === 'temporarily'"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<UnitSetting
|
||||||
|
path="onMuteDefaultAction"
|
||||||
|
unit-set="time"
|
||||||
|
:units="['s', 'm', 'h', 'd']"
|
||||||
|
:min="0"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.default_expiration_time') }}
|
||||||
|
</UnitSetting>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li v-if="blockExpirationSupported">
|
||||||
|
{{ $t('user_card.default_block_expiration') }}
|
||||||
|
<Select
|
||||||
|
id="onBlockDefaultActionLv1"
|
||||||
|
v-model="onBlockDefaultActionLv1"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in muteBlockLv1Options"
|
||||||
|
:key="option.key"
|
||||||
|
:value="option.value"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</Select>
|
||||||
|
<ul
|
||||||
|
class="setting-list suboptions"
|
||||||
|
v-if="onBlockDefaultActionLv1 === 'temporarily'"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<UnitSetting
|
||||||
|
path="onBlockDefaultAction"
|
||||||
|
unit-set="time"
|
||||||
|
:units="['s', 'm', 'h', 'd']"
|
||||||
|
:min="0"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.default_expiration_time') }}
|
||||||
|
</UnitSetting>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<BooleanSetting path="hideFilteredStatuses">
|
<BooleanSetting path="hideFilteredStatuses">
|
||||||
{{ $t('settings.hide_muted_statuses') }}
|
{{ $t('settings.hide_muted_statuses') }}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ import IntegerSetting from '../helpers/integer_setting.vue'
|
||||||
import FloatSetting from '../helpers/float_setting.vue'
|
import FloatSetting from '../helpers/float_setting.vue'
|
||||||
import UnitSetting from '../helpers/unit_setting.vue'
|
import UnitSetting from '../helpers/unit_setting.vue'
|
||||||
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
||||||
|
import Select from 'src/components/select/select.vue'
|
||||||
|
|
||||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||||
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
||||||
|
|
||||||
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
|
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
|
@ -73,7 +75,8 @@ const GeneralTab = {
|
||||||
UnitSetting,
|
UnitSetting,
|
||||||
InterfaceLanguageSwitcher,
|
InterfaceLanguageSwitcher,
|
||||||
ScopeSelector,
|
ScopeSelector,
|
||||||
ProfileSettingIndicator
|
ProfileSettingIndicator,
|
||||||
|
Select
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
postFormats () {
|
postFormats () {
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,10 @@
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<BooleanSetting path="modalOnBlock">
|
<BooleanSetting v-if="!blockExpirationSupported" path="modalOnBlock">
|
||||||
{{ $t('settings.confirm_dialogs_block') }}
|
{{ $t('settings.confirm_dialogs_block') }}
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<BooleanSetting path="modalOnMute">
|
|
||||||
{{ $t('settings.confirm_dialogs_mute') }}
|
|
||||||
</BooleanSetting>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<BooleanSetting path="modalOnMuteConversation">
|
<BooleanSetting path="modalOnMuteConversation">
|
||||||
{{ $t('settings.confirm_dialogs_mute_conversation') }}
|
{{ $t('settings.confirm_dialogs_mute_conversation') }}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import ActionButton from './action_button.vue'
|
import ActionButton from './action_button.vue'
|
||||||
import Popover from 'src/components/popover/popover.vue'
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
import MuteConfirm from 'src/components/confirm_modal/mute_confirm.vue'
|
import MuteConfirm from 'src/components/confirm_modal/mute_confirm.vue'
|
||||||
|
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
|
@ -19,7 +20,8 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
ActionButton,
|
ActionButton,
|
||||||
Popover,
|
Popover,
|
||||||
MuteConfirm
|
MuteConfirm,
|
||||||
|
UserTimedFilterModal
|
||||||
},
|
},
|
||||||
props: ['button', 'status'],
|
props: ['button', 'status'],
|
||||||
emits: ['interacted'],
|
emits: ['interacted'],
|
||||||
|
|
|
||||||
|
|
@ -94,11 +94,10 @@
|
||||||
:status="status"
|
:status="status"
|
||||||
:user="user"
|
:user="user"
|
||||||
/>
|
/>
|
||||||
<MuteConfirm
|
<UserTimedFilterModal
|
||||||
ref="confirmUser"
|
:is-mute="true"
|
||||||
type="user"
|
|
||||||
:status="status"
|
|
||||||
:user="user"
|
:user="user"
|
||||||
|
ref="confirmUser"
|
||||||
/>
|
/>
|
||||||
</teleport>
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ import UserNote from '../user_note/user_note.vue'
|
||||||
import Select from '../select/select.vue'
|
import Select from '../select/select.vue'
|
||||||
import UserLink from '../user_link/user_link.vue'
|
import UserLink from '../user_link/user_link.vue'
|
||||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||||
import MuteConfirm from '../confirm_modal/mute_confirm.vue'
|
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.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 { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { usePostStatusStore } from 'src/stores/post_status'
|
import { usePostStatusStore } from 'src/stores/post_status'
|
||||||
|
|
@ -48,6 +49,19 @@ export default {
|
||||||
'onClose',
|
'onClose',
|
||||||
'hasNoteEditor'
|
'hasNoteEditor'
|
||||||
],
|
],
|
||||||
|
components: {
|
||||||
|
UserAvatar,
|
||||||
|
RemoteFollow,
|
||||||
|
ModerationTools,
|
||||||
|
AccountActions,
|
||||||
|
ProgressButton,
|
||||||
|
FollowButton,
|
||||||
|
Select,
|
||||||
|
RichContent,
|
||||||
|
UserLink,
|
||||||
|
UserNote,
|
||||||
|
UserTimedFilterModal
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
followRequestInProgress: false,
|
followRequestInProgress: false,
|
||||||
|
|
@ -145,24 +159,27 @@ export default {
|
||||||
supportsNote () {
|
supportsNote () {
|
||||||
return 'note' in this.relationship
|
return 'note' in this.relationship
|
||||||
},
|
},
|
||||||
...mapGetters(['mergedConfig'])
|
muteExpiryAvailable () {
|
||||||
|
return this.user.mute_expires_at !== undefined
|
||||||
},
|
},
|
||||||
components: {
|
muteExpiry () {
|
||||||
UserAvatar,
|
return this.user.mute_expires_at == null
|
||||||
RemoteFollow,
|
? this.$t('user_card.mute_expires_forever')
|
||||||
ModerationTools,
|
: this.$t('user_card.mute_expires_at', [new Date(this.user.mute_expires_at).toLocaleString()])
|
||||||
AccountActions,
|
},
|
||||||
ProgressButton,
|
blockExpiryAvailable () {
|
||||||
FollowButton,
|
return this.user.block_expires_at !== undefined
|
||||||
Select,
|
},
|
||||||
RichContent,
|
blockExpiry () {
|
||||||
UserLink,
|
return this.user.block_expires_at == null
|
||||||
UserNote,
|
? this.$t('user_card.block_expires_forever')
|
||||||
MuteConfirm
|
: this.$t('user_card.block_expires_at', [new Date(this.user.mute_expires_at).toLocaleString()])
|
||||||
|
},
|
||||||
|
...mapGetters(['mergedConfig'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
muteUser () {
|
muteUser () {
|
||||||
this.$refs.confirmation.optionallyPrompt()
|
this.$refs.timedMuteDialog.optionallyPrompt()
|
||||||
},
|
},
|
||||||
unmuteUser () {
|
unmuteUser () {
|
||||||
this.$store.dispatch('unmuteUser', this.user.id)
|
this.$store.dispatch('unmuteUser', this.user.id)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@
|
||||||
--_still-image-label-visibility: hidden;
|
--_still-image-label-visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-mute, .btn-mention {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-heading {
|
.panel-heading {
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,18 @@
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="relationship.muting && muteExpiryAvailable"
|
||||||
|
class="alert neutral user-role"
|
||||||
|
>
|
||||||
|
{{ muteExpiry }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="relationship.blocking && blockExpiryAvailable"
|
||||||
|
class="alert neutral user-role"
|
||||||
|
>
|
||||||
|
{{ blockExpiry }}
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="!mergedConfig.hideUserStats && !hideBio"
|
v-if="!mergedConfig.hideUserStats && !hideBio"
|
||||||
class="dailyAvg"
|
class="dailyAvg"
|
||||||
|
|
@ -232,7 +244,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
v-if="relationship.muting"
|
v-if="relationship.muting"
|
||||||
class="btn button-default btn-block toggled"
|
class="btn button-default btn-mute toggled"
|
||||||
:disabled="user.deactivated"
|
:disabled="user.deactivated"
|
||||||
@click="unmuteUser"
|
@click="unmuteUser"
|
||||||
>
|
>
|
||||||
|
|
@ -240,7 +252,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default btn-block"
|
class="btn button-default btn-mute"
|
||||||
:disabled="user.deactivated"
|
:disabled="user.deactivated"
|
||||||
@click="muteUser"
|
@click="muteUser"
|
||||||
>
|
>
|
||||||
|
|
@ -249,7 +261,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="btn button-default btn-block"
|
class="btn button-default btn-mention"
|
||||||
:disabled="user.deactivated"
|
:disabled="user.deactivated"
|
||||||
@click="mentionUser"
|
@click="mentionUser"
|
||||||
>
|
>
|
||||||
|
|
@ -314,10 +326,10 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<teleport to="#modal">
|
<teleport to="#modal">
|
||||||
<MuteConfirm
|
<UserTimedFilterModal
|
||||||
ref="confirmation"
|
|
||||||
type="user"
|
|
||||||
:user="user"
|
:user="user"
|
||||||
|
:is-mute="true"
|
||||||
|
ref="timedMuteDialog"
|
||||||
/>
|
/>
|
||||||
</teleport>
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
import Select from 'src/components/select/select.vue'
|
||||||
|
import { durationStrToMs } from 'src/services/date_utils/date_utils.js'
|
||||||
|
|
||||||
|
const UserTimedFilterModal = {
|
||||||
|
data () {
|
||||||
|
const action = this.isMute
|
||||||
|
? this.$store.getters.mergedConfig.onMuteDefaultAction
|
||||||
|
: this.$store.getters.mergedConfig.onBlockDefaultAction
|
||||||
|
const doAsk = action === 'ask'
|
||||||
|
const defaultValues = {}
|
||||||
|
|
||||||
|
if (doAsk || action === 'forever') {
|
||||||
|
defaultValues.expiration = 14
|
||||||
|
defaultValues.expirationUnit = 'd'
|
||||||
|
if (action === 'forever') {
|
||||||
|
defaultValues.forever = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const unit = action.replace(/[0-9,.]+/, '')
|
||||||
|
const value = action.replace(/[^0-9,.]+/, '')
|
||||||
|
defaultValues.expiration = value
|
||||||
|
defaultValues.expirationUnit = unit
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
showing: false,
|
||||||
|
forever: false,
|
||||||
|
dontAskAgain: false,
|
||||||
|
...defaultValues
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ConfirmModal,
|
||||||
|
Select,
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
isMute: Boolean,
|
||||||
|
user: Object
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
shouldConfirm () {
|
||||||
|
if (this.isMute) {
|
||||||
|
return this.$store.getters.mergedConfig.onMuteDefaultAction === 'ask'
|
||||||
|
} else {
|
||||||
|
return this.$store.getters.mergedConfig.onBlockDefaultAction === 'ask'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expiryString () {
|
||||||
|
return this.expiration.toString() + this.expirationUnit
|
||||||
|
},
|
||||||
|
expirySeconds () {
|
||||||
|
return Math.floor(durationStrToMs(this.expiryString) / 1000)
|
||||||
|
},
|
||||||
|
requestBody () {
|
||||||
|
const object = { id: this.user.id }
|
||||||
|
if (!this.forever) {
|
||||||
|
object.expiresIn = this.expirySeconds
|
||||||
|
}
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
expiration (newVal) {
|
||||||
|
if (newVal <= 0) {
|
||||||
|
this.expiration = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
optionallyPrompt () {
|
||||||
|
if (this.shouldConfirm) {
|
||||||
|
this.showing = true
|
||||||
|
} else {
|
||||||
|
this.accept()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accept () {
|
||||||
|
if (this.isMute) {
|
||||||
|
this.$store.dispatch('muteUser', this.requestBody)
|
||||||
|
if (this.dontAskAgain) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'onMuteDefaultAction', value: this.expiryString })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('blockUser', this.requestBody)
|
||||||
|
if (this.dontAskAgain) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'onBlockDefaultAction', value: this.expiryString })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.showing = false
|
||||||
|
},
|
||||||
|
cancel () {
|
||||||
|
this.showing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserTimedFilterModal
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
.UserTimedFilterModal {
|
||||||
|
.input-dont-ask-again {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-expire-at {
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-left-checkbox {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<template>
|
||||||
|
<confirm-modal
|
||||||
|
v-if="showing"
|
||||||
|
class="UserTimedFilterModal"
|
||||||
|
:title="$t(isMute ? $t('user_card.mute') : $t('user_card.block'))"
|
||||||
|
:confirm-text="$t(isMute ? 'user_card.mute_confirm_accept_button' : 'user_card.block_confirm_accept_button')"
|
||||||
|
:cancel-text="$t(isMute ? 'user_card.mute_confirm_cancel_button' : 'user_card.block_confirm_cancel_button')"
|
||||||
|
@accepted="accept"
|
||||||
|
@cancelled="cancel"
|
||||||
|
>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{ $t(isMute ? 'user_card.expire_mute_message' : 'user_card.expire_block_message', [user.screen_name]) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ $t('user_card.expire_in') }}
|
||||||
|
<input
|
||||||
|
id="userFilterExpires"
|
||||||
|
class="input input-expire-in"
|
||||||
|
:class="{ disabled: forever }"
|
||||||
|
v-model="expiration"
|
||||||
|
:disabled="forever"
|
||||||
|
min="1"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
id="userFilterExpiresUnit"
|
||||||
|
v-model="expirationUnit"
|
||||||
|
class="input unit-input unstyled"
|
||||||
|
:disabled="forever"
|
||||||
|
>
|
||||||
|
<option key="s" value="s"> {{ $t('time.unit.seconds_suffix') }} </option>
|
||||||
|
<option key="m" value="m"> {{ $t('time.unit.minutes_suffix') }} </option>
|
||||||
|
<option key="h" value="h"> {{ $t('time.unit.hours_suffix') }} </option>
|
||||||
|
<option key="d" value="d"> {{ $t('time.unit.days_suffix') }} </option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{{ $t('user_card.mute_or') }}
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
id="forever"
|
||||||
|
v-model="forever"
|
||||||
|
name="forever"
|
||||||
|
class="input-forever"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.mute_block_never') }}
|
||||||
|
</Checkbox>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<template #footerLeft>
|
||||||
|
<div class="footer-left-checkbox">
|
||||||
|
<Checkbox
|
||||||
|
id="dontAskAgain"
|
||||||
|
v-model="dontAskAgain"
|
||||||
|
name="dontAskAgain"
|
||||||
|
class="input-dont-ask-again"
|
||||||
|
>
|
||||||
|
{{ $t(isMute ? 'user_card.dont_ask_again_mute' : 'user_card.dont_ask_again_block') }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</confirm-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./user_timed_filter_modal.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss" src="./user_timed_filter_modal.scss" />
|
||||||
|
|
@ -1233,18 +1233,25 @@
|
||||||
"unit": {
|
"unit": {
|
||||||
"days": "{0} day | {0} days",
|
"days": "{0} day | {0} days",
|
||||||
"days_short": "{0}d",
|
"days_short": "{0}d",
|
||||||
|
"days_suffix": "day(s)",
|
||||||
"hours": "{0} hour | {0} hours",
|
"hours": "{0} hour | {0} hours",
|
||||||
"hours_short": "{0}h",
|
"hours_short": "{0}h",
|
||||||
|
"hours_suffix": "hour(s)",
|
||||||
"minutes": "{0} minute | {0} minutes",
|
"minutes": "{0} minute | {0} minutes",
|
||||||
"minutes_short": "{0}min",
|
"minutes_short": "{0}min",
|
||||||
|
"minutes_suffix": "minute(s)",
|
||||||
"months": "{0} month | {0} months",
|
"months": "{0} month | {0} months",
|
||||||
"months_short": "{0}mo",
|
"months_short": "{0}mo",
|
||||||
|
"months_suffix": "month(s)",
|
||||||
"seconds": "{0} second | {0} seconds",
|
"seconds": "{0} second | {0} seconds",
|
||||||
"seconds_short": "{0}s",
|
"seconds_short": "{0}s",
|
||||||
|
"seconds_suffix": "second(s)",
|
||||||
"weeks": "{0} week | {0} weeks",
|
"weeks": "{0} week | {0} weeks",
|
||||||
"weeks_short": "{0}w",
|
"weeks_short": "{0}w",
|
||||||
|
"weeks_suffix": "week(s)",
|
||||||
"years": "{0} year | {0} years",
|
"years": "{0} year | {0} years",
|
||||||
"years_short": "{0}y"
|
"years_short": "{0}y",
|
||||||
|
"years": "year(s)"
|
||||||
},
|
},
|
||||||
"in_future": "in {0}",
|
"in_future": "in {0}",
|
||||||
"in_past": "{0} ago",
|
"in_past": "{0} ago",
|
||||||
|
|
@ -1398,6 +1405,23 @@
|
||||||
"mute_confirm": "Do you really want to mute {user}?",
|
"mute_confirm": "Do you really want to mute {user}?",
|
||||||
"mute_confirm_accept_button": "Mute",
|
"mute_confirm_accept_button": "Mute",
|
||||||
"mute_confirm_cancel_button": "Do not mute",
|
"mute_confirm_cancel_button": "Do not mute",
|
||||||
|
"mute_or": "or",
|
||||||
|
"expire_in": "Expire in",
|
||||||
|
"expire_mute_message": "Are you sure you want to mute {0}?",
|
||||||
|
"expire_block_message": "Are you sure you want to block {0}?",
|
||||||
|
"dont_ask_again_mute": "Always mute users this way",
|
||||||
|
"dont_ask_again_block": "Always block users this way",
|
||||||
|
"mute_block_temporarily": "Temporarily",
|
||||||
|
"mute_block_forever": "Forever",
|
||||||
|
"mute_block_never": "Never",
|
||||||
|
"mute_block_ask": "Ask",
|
||||||
|
"default_mute_expiration": "Always mute users",
|
||||||
|
"default_block_expiration": "Always block users",
|
||||||
|
"default_expiration_time": "Expire in",
|
||||||
|
"mute_expires_forever": "Muted forever",
|
||||||
|
"mute_expires_at": "Muted until {0}",
|
||||||
|
"block_expires_forever": "Blocked forever",
|
||||||
|
"block_expires_at": "Blocked until {0}",
|
||||||
"mute_duration_prompt": "Mute this user for (0 for indefinite time):",
|
"mute_duration_prompt": "Mute this user for (0 for indefinite time):",
|
||||||
"per_day": "per day",
|
"per_day": "per day",
|
||||||
"remote_follow": "Remote follow",
|
"remote_follow": "Remote follow",
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,11 @@ export const defaultState = {
|
||||||
alwaysShowSubjectInput: undefined, // instance default
|
alwaysShowSubjectInput: undefined, // instance default
|
||||||
postContentType: undefined, // instance default
|
postContentType: undefined, // instance default
|
||||||
minimalScopesMode: undefined, // instance default
|
minimalScopesMode: undefined, // instance default
|
||||||
|
|
||||||
// This hides statuses filtered via a word filter
|
// This hides statuses filtered via a word filter
|
||||||
hideFilteredStatuses: undefined, // instance default
|
hideFilteredStatuses: undefined, // instance default
|
||||||
|
|
||||||
|
// Confirmations
|
||||||
modalOnRepeat: undefined, // instance default
|
modalOnRepeat: undefined, // instance default
|
||||||
modalOnUnfollow: undefined, // instance default
|
modalOnUnfollow: undefined, // instance default
|
||||||
modalOnBlock: undefined, // instance default
|
modalOnBlock: undefined, // instance default
|
||||||
|
|
@ -110,6 +113,11 @@ export const defaultState = {
|
||||||
modalOnApproveFollow: undefined, // instance default
|
modalOnApproveFollow: undefined, // instance default
|
||||||
modalOnDenyFollow: undefined, // instance default
|
modalOnDenyFollow: undefined, // instance default
|
||||||
modalOnRemoveUserFromFollowers: undefined, // instance default
|
modalOnRemoveUserFromFollowers: undefined, // instance default
|
||||||
|
|
||||||
|
// Expiry confirmations/default actions
|
||||||
|
onMuteDefaultAction: 'ask',
|
||||||
|
onBlockDefaultAction: 'ask',
|
||||||
|
|
||||||
modalMobileCenter: undefined,
|
modalMobileCenter: undefined,
|
||||||
playVideosInModal: false,
|
playVideosInModal: false,
|
||||||
useOneClickNsfw: false,
|
useOneClickNsfw: false,
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ const defaultState = {
|
||||||
suggestionsWeb: '',
|
suggestionsWeb: '',
|
||||||
quotingAvailable: false,
|
quotingAvailable: false,
|
||||||
groupActorAvailable: false,
|
groupActorAvailable: false,
|
||||||
|
blockExpiration: false,
|
||||||
localBubbleInstances: [], // Akkoma
|
localBubbleInstances: [], // Akkoma
|
||||||
|
|
||||||
// Html stuff
|
// Html stuff
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,19 @@ const getNotificationPermission = () => {
|
||||||
return Promise.resolve(Notification.permission)
|
return Promise.resolve(Notification.permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockUser = (store, id) => {
|
const blockUser = (store, args) => {
|
||||||
return store.rootState.api.backendInteractor.blockUser({ id })
|
const id = args.id
|
||||||
|
const expiresIn = typeof args === 'object' ? args.expiresIn : 0
|
||||||
|
|
||||||
|
const predictedRelationship = store.state.relationships[id] || { id }
|
||||||
|
store.commit('updateUserRelationship', [predictedRelationship])
|
||||||
|
store.commit('addBlockId', id)
|
||||||
|
|
||||||
|
return store.rootState.api.backendInteractor.blockUser({ id, expiresIn })
|
||||||
.then((relationship) => {
|
.then((relationship) => {
|
||||||
store.commit('updateUserRelationship', [relationship])
|
store.commit('updateUserRelationship', [relationship])
|
||||||
store.commit('addBlockId', id)
|
store.commit('addBlockId', id)
|
||||||
|
|
||||||
store.commit('removeStatus', { timeline: 'friends', userId: id })
|
store.commit('removeStatus', { timeline: 'friends', userId: id })
|
||||||
store.commit('removeStatus', { timeline: 'public', userId: id })
|
store.commit('removeStatus', { timeline: 'public', userId: id })
|
||||||
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: id })
|
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: id })
|
||||||
|
|
@ -74,7 +82,6 @@ const muteUser = (store, args) => {
|
||||||
const expiresIn = typeof args === 'object' ? args.expiresIn : 0
|
const expiresIn = typeof args === 'object' ? args.expiresIn : 0
|
||||||
|
|
||||||
const predictedRelationship = store.state.relationships[id] || { id }
|
const predictedRelationship = store.state.relationships[id] || { id }
|
||||||
predictedRelationship.muting = true
|
|
||||||
store.commit('updateUserRelationship', [predictedRelationship])
|
store.commit('updateUserRelationship', [predictedRelationship])
|
||||||
store.commit('addMuteId', id)
|
store.commit('addMuteId', id)
|
||||||
|
|
||||||
|
|
@ -360,20 +367,20 @@ const users = {
|
||||||
return blocks
|
return blocks
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
blockUser (store, id) {
|
blockUser (store, data) {
|
||||||
return blockUser(store, id)
|
return blockUser(store, data)
|
||||||
},
|
},
|
||||||
unblockUser (store, id) {
|
unblockUser (store, data) {
|
||||||
return unblockUser(store, id)
|
return unblockUser(store, data)
|
||||||
},
|
},
|
||||||
removeUserFromFollowers (store, id) {
|
removeUserFromFollowers (store, id) {
|
||||||
return removeUserFromFollowers(store, id)
|
return removeUserFromFollowers(store, id)
|
||||||
},
|
},
|
||||||
blockUsers (store, ids = []) {
|
blockUsers (store, data = []) {
|
||||||
return Promise.all(ids.map(id => blockUser(store, id)))
|
return Promise.all(data.map(d => blockUser(store, d)))
|
||||||
},
|
},
|
||||||
unblockUsers (store, ids = []) {
|
unblockUsers (store, data = []) {
|
||||||
return Promise.all(ids.map(id => unblockUser(store, id)))
|
return Promise.all(data.map(d => unblockUser(store, d)))
|
||||||
},
|
},
|
||||||
editUserNote (store, args) {
|
editUserNote (store, args) {
|
||||||
return editUserNote(store, args)
|
return editUserNote(store, args)
|
||||||
|
|
@ -396,8 +403,8 @@ const users = {
|
||||||
return mutes
|
return mutes
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
muteUser (store, id) {
|
muteUser (store, data) {
|
||||||
return muteUser(store, id)
|
return muteUser(store, data)
|
||||||
},
|
},
|
||||||
unmuteUser (store, id) {
|
unmuteUser (store, id) {
|
||||||
return unmuteUser(store, id)
|
return unmuteUser(store, id)
|
||||||
|
|
@ -408,11 +415,11 @@ const users = {
|
||||||
showReblogs (store, id) {
|
showReblogs (store, id) {
|
||||||
return showReblogs(store, id)
|
return showReblogs(store, id)
|
||||||
},
|
},
|
||||||
muteUsers (store, ids = []) {
|
muteUsers (store, data = []) {
|
||||||
return Promise.all(ids.map(id => muteUser(store, id)))
|
return Promise.all(data.map(d => muteUser(store, d)))
|
||||||
},
|
},
|
||||||
unmuteUsers (store, ids = []) {
|
unmuteUsers (store, ids = []) {
|
||||||
return Promise.all(ids.map(id => unmuteUser(store, id)))
|
return Promise.all(ids.map(d => unmuteUser(store, d)))
|
||||||
},
|
},
|
||||||
fetchDomainMutes (store) {
|
fetchDomainMutes (store) {
|
||||||
return store.rootState.api.backendInteractor.fetchDomainMutes()
|
return store.rootState.api.backendInteractor.fetchDomainMutes()
|
||||||
|
|
|
||||||
|
|
@ -320,11 +320,18 @@ const unmuteConversation = ({ id, credentials }) => {
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockUser = ({ id, credentials }) => {
|
const blockUser = ({ id, expiresIn, credentials }) => {
|
||||||
return fetch(MASTODON_BLOCK_USER_URL(id), {
|
const payload = {}
|
||||||
headers: authHeaders(credentials),
|
if (expiresIn) {
|
||||||
method: 'POST'
|
payload.expires_in = expiresIn
|
||||||
}).then((data) => data.json())
|
}
|
||||||
|
|
||||||
|
return promisedRequest({
|
||||||
|
url: MASTODON_BLOCK_USER_URL(id),
|
||||||
|
credentials,
|
||||||
|
method: 'POST',
|
||||||
|
payload
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const unblockUser = ({ id, credentials }) => {
|
const unblockUser = ({ id, credentials }) => {
|
||||||
|
|
@ -1174,7 +1181,13 @@ const muteUser = ({ id, expiresIn, credentials }) => {
|
||||||
if (expiresIn) {
|
if (expiresIn) {
|
||||||
payload.expires_in = expiresIn
|
payload.expires_in = expiresIn
|
||||||
}
|
}
|
||||||
return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST', payload })
|
|
||||||
|
return promisedRequest({
|
||||||
|
url: MASTODON_MUTE_USER_URL(id),
|
||||||
|
credentials,
|
||||||
|
method: 'POST',
|
||||||
|
payload
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const unmuteUser = ({ id, credentials }) => {
|
const unmuteUser = ({ id, credentials }) => {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ export const parseUser = (data) => {
|
||||||
output.screen_name = data.acct
|
output.screen_name = data.acct
|
||||||
output.fqn = data.fqn
|
output.fqn = data.fqn
|
||||||
output.statusnet_profile_url = data.url
|
output.statusnet_profile_url = data.url
|
||||||
|
output.mute_expires_at = data.mute_expires_at
|
||||||
|
output.block_expires_at = data.block_expires_at
|
||||||
|
|
||||||
// There's nothing else to get
|
// There's nothing else to get
|
||||||
if (mastoShort) {
|
if (mastoShort) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue