diff --git a/changelog.d/timed.add b/changelog.d/timed.add
new file mode 100644
index 000000000..66cb50c9f
--- /dev/null
+++ b/changelog.d/timed.add
@@ -0,0 +1 @@
+Support for expiring mutes and blocks (if available)
diff --git a/src/App.scss b/src/App.scss
index 1b3133a7c..d56306c9e 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -679,11 +679,6 @@ option {
}
}
-.btn-block {
- display: block;
- width: 100%;
-}
-
.btn-group {
position: relative;
display: inline-flex;
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 65c4c9205..fd4c57229 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -274,6 +274,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
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: 'blockExpiration', value: features.includes('pleroma:block_expiration') })
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
const uploadLimits = metadata.uploadLimits
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index 9a63f57eb..f8ad0e11b 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -3,6 +3,7 @@ import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
import UserListMenu from 'src/components/user_list_menu/user_list_menu.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 {
faEllipsisV
@@ -27,15 +28,10 @@ const AccountActions = {
ProgressButton,
Popover,
UserListMenu,
- ConfirmModal
+ ConfirmModal,
+ UserTimedFilterModal
},
methods: {
- showConfirmBlock () {
- this.showingConfirmBlock = true
- },
- hideConfirmBlock () {
- this.showingConfirmBlock = false
- },
showConfirmRemoveUserFromFollowers () {
this.showingConfirmRemoveFollower = true
},
@@ -49,10 +45,14 @@ const AccountActions = {
this.$store.dispatch('hideReblogs', this.user.id)
},
blockUser () {
- if (!this.shouldConfirmBlock) {
- this.doBlockUser()
+ if (this.$refs.timedBlockDialog) {
+ this.$refs.timedBlockDialog.optionallyPrompt()
} else {
- this.showConfirmBlock()
+ if (!this.shouldConfirmBlock) {
+ this.doBlockUser()
+ } else {
+ this.showingConfirmBlock = true
+ }
}
},
doBlockUser () {
@@ -91,6 +91,7 @@ const AccountActions = {
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
},
...mapState({
+ blockExpirationSupported: state => state.instance.blockExpiration,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})
}
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index fd4837ee4..f3cca45d0 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -96,7 +96,8 @@
+
diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js
index 0bf4e37bc..b5be9e7b7 100644
--- a/src/components/block_card/block_card.js
+++ b/src/components/block_card/block_card.js
@@ -1,12 +1,9 @@
+import { mapState } from 'vuex'
+
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const BlockCard = {
props: ['userId'],
- data () {
- return {
- progress: false
- }
- },
computed: {
user () {
return this.$store.getters.findUser(this.userId)
@@ -16,23 +13,32 @@ const BlockCard = {
},
blocked () {
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: {
BasicUserCard
},
methods: {
unblockUser () {
- this.progress = true
- this.$store.dispatch('unblockUser', this.user.id).then(() => {
- this.progress = false
- })
+ this.$store.dispatch('unblockUser', this.user.id)
},
blockUser () {
- this.progress = true
- this.$store.dispatch('blockUser', this.user.id).then(() => {
- this.progress = false
- })
+ if (this.blockExpirationSupported) {
+ this.$refs.timedBlockDialog.optionallyPrompt()
+ } else {
+ this.$store.dispatch('blockUser', this.user.id)
+ }
}
}
}
diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue
index b14ef8448..9f64a8e8c 100644
--- a/src/components/block_card/block_card.vue
+++ b/src/components/block_card/block_card.vue
@@ -1,33 +1,32 @@
+
+ {{ blockExpiry }}
+
+ {{ ' ' }}
+
+
+
diff --git a/src/components/confirm_modal/confirm_modal.vue b/src/components/confirm_modal/confirm_modal.vue
index 9af00edc4..992792ede 100644
--- a/src/components/confirm_modal/confirm_modal.vue
+++ b/src/components/confirm_modal/confirm_modal.vue
@@ -11,6 +11,7 @@
+
-
-
-
-
- {{ ' ' }}
-
-
-
diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js
index cbec0e9bb..895586888 100644
--- a/src/components/mute_card/mute_card.js
+++ b/src/components/mute_card/mute_card.js
@@ -1,12 +1,8 @@
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 = {
props: ['userId'],
- data () {
- return {
- progress: false
- }
- },
computed: {
user () {
return this.$store.getters.findUser(this.userId)
@@ -16,23 +12,26 @@ const MuteCard = {
},
muted () {
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: {
- BasicUserCard
+ BasicUserCard,
+ UserTimedFilterModal
},
methods: {
unmuteUser () {
- this.progress = true
- this.$store.dispatch('unmuteUser', this.userId).then(() => {
- this.progress = false
- })
+ this.$store.dispatch('unmuteUser', this.userId)
},
muteUser () {
- this.progress = true
- this.$store.dispatch('muteUser', this.userId).then(() => {
- this.progress = false
- })
+ this.$refs.timedMuteDialog.optionallyPrompt()
}
}
}
diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue
index 97e91cbca..60638d3ba 100644
--- a/src/components/mute_card/mute_card.vue
+++ b/src/components/mute_card/mute_card.vue
@@ -1,33 +1,32 @@
+
+ {{ muteExpiry }}
+
+ {{ ' ' }}
+
+
+
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index 5ff137062..9a4ed814d 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,5 +1,6 @@
import { cloneDeep } from 'lodash'
import { mapState, mapActions } from 'pinia'
+import { mapState as mapVuexState } from 'vuex'
import { v4 as uuidv4 } from 'uuid';
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
@@ -30,6 +31,11 @@ const FilteringTab = {
value: 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),
muteFiltersDraftDirty: Object.fromEntries(
Object.entries(
@@ -93,6 +99,43 @@ const FilteringTab = {
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 () {
return Object.entries(this.muteFiltersDraftObject)
},
@@ -107,7 +150,7 @@ const FilteringTab = {
...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']),
getDatetimeLocal (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 = [
date.getFullYear(),
'-',
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index 6db82d3c4..2b05d1906 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -80,6 +80,66 @@
{{ $t('settings.filter.mute_filter') }}
+ -
+ {{ $t('user_card.default_mute_expiration') }}
+
+
+ -
+
+ {{ $t('user_card.default_expiration_time') }}
+
+
+
+
+ -
+ {{ $t('user_card.default_block_expiration') }}
+
+
+ -
+
+ {{ $t('user_card.default_expiration_time') }}
+
+
+
+
-
{{ $t('settings.hide_muted_statuses') }}
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index df28a23e3..879dfd1a3 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -5,9 +5,11 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import FloatSetting from '../helpers/float_setting.vue'
import UnitSetting from '../helpers/unit_setting.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 ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
+
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -73,7 +75,8 @@ const GeneralTab = {
UnitSetting,
InterfaceLanguageSwitcher,
ScopeSelector,
- ProfileSettingIndicator
+ ProfileSettingIndicator,
+ Select
},
computed: {
postFormats () {
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index b8da782fb..40d40bc3e 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -107,15 +107,10 @@
-
-
+
{{ $t('settings.confirm_dialogs_block') }}
- -
-
- {{ $t('settings.confirm_dialogs_mute') }}
-
-
-
{{ $t('settings.confirm_dialogs_mute_conversation') }}
diff --git a/src/components/status_action_buttons/action_button_container.js b/src/components/status_action_buttons/action_button_container.js
index 313e3022f..a8f20800b 100644
--- a/src/components/status_action_buttons/action_button_container.js
+++ b/src/components/status_action_buttons/action_button_container.js
@@ -1,6 +1,7 @@
import ActionButton from './action_button.vue'
import Popover from 'src/components/popover/popover.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 {
@@ -19,7 +20,8 @@ export default {
components: {
ActionButton,
Popover,
- MuteConfirm
+ MuteConfirm,
+ UserTimedFilterModal
},
props: ['button', 'status'],
emits: ['interacted'],
diff --git a/src/components/status_action_buttons/action_button_container.vue b/src/components/status_action_buttons/action_button_container.vue
index 931a40349..75f5010ee 100644
--- a/src/components/status_action_buttons/action_button_container.vue
+++ b/src/components/status_action_buttons/action_button_container.vue
@@ -94,11 +94,10 @@
:status="status"
:user="user"
/>
-
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 55c76225c..353a891f6 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -8,7 +8,8 @@ import UserNote from '../user_note/user_note.vue'
import Select from '../select/select.vue'
import UserLink from '../user_link/user_link.vue'
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 { mapGetters } from 'vuex'
import { usePostStatusStore } from 'src/stores/post_status'
@@ -48,6 +49,19 @@ export default {
'onClose',
'hasNoteEditor'
],
+ components: {
+ UserAvatar,
+ RemoteFollow,
+ ModerationTools,
+ AccountActions,
+ ProgressButton,
+ FollowButton,
+ Select,
+ RichContent,
+ UserLink,
+ UserNote,
+ UserTimedFilterModal
+ },
data () {
return {
followRequestInProgress: false,
@@ -145,24 +159,27 @@ export default {
supportsNote () {
return 'note' in this.relationship
},
+ 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()])
+ },
+ 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()])
+ },
...mapGetters(['mergedConfig'])
},
- components: {
- UserAvatar,
- RemoteFollow,
- ModerationTools,
- AccountActions,
- ProgressButton,
- FollowButton,
- Select,
- RichContent,
- UserLink,
- UserNote,
- MuteConfirm
- },
methods: {
muteUser () {
- this.$refs.confirmation.optionallyPrompt()
+ this.$refs.timedMuteDialog.optionallyPrompt()
},
unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)
diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss
index d263be1c0..418bef08b 100644
--- a/src/components/user_card/user_card.scss
+++ b/src/components/user_card/user_card.scss
@@ -8,6 +8,11 @@
--_still-image-label-visibility: hidden;
}
+ .btn-mute, .btn-mention {
+ display: block;
+ width: 100%;
+ }
+
.panel-heading {
padding: 0.5em 0;
text-align: center;
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 36935424d..e51a16271 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -136,6 +136,18 @@
size="sm"
/>
+
+ {{ muteExpiry }}
+
+
+ {{ blockExpiry }}
+